/*
 * Copyright (C) 2005, Julien Couvreur, julien.couvreur (at) gmail.com
 * http://blog.monstuff.com
 * 
 * "XMLHttpRequest Debugging for IE" is a port of the "XMLHttpRequest Debugging" 
 * Greasemonkey script for Firefox.
 * You can find more information and the latest updates from:
 * http://blog.monstuff.com/archives/000291.html
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */



/*
Changes from original GM script:
    e.target -> e.srcElement
    createIEaddEventListeners
    startTracing
    GM_* functions

    window.document.documentElement.appendChild(uiDiv); -> document.body.appendChild(uiDiv);
    Fixed AddGlobalStyle
    Fixed FAT
    Added userData

TODO:
    Fix [edit&replay] bug
    Fix [export] bug
*/

// XMLHttpRequest Debugging bookmarklet
// javascript:function loadScript(scriptURL) { var scriptElem = document.createElement('SCRIPT'); scriptElem.setAttribute('language', 'JavaScript'); scriptElem.setAttribute('src', scriptURL); document.body.appendChild(scriptElem);} loadScript('http://blog.monstuff.com/archives/images/XHR-Debugging-IE.js');

/*============================================================================*/
// Emulate addEventListener in IE
function createIEaddEventListeners()
{
    if (document.addEventListener || !document.attachEvent)
        return;

    function ieAddEventListener(eventName, handler, capture)
    {
        if (this.attachEvent)
            this.attachEvent('on' + eventName, handler);
    }

    function attachToAll()
    {
        var i, l = document.all.length;

        for (i = 0; i < l; i++)
            if (document.all[i].attachEvent)
                document.all[i].addEventListener = ieAddEventListener;
    }

    var originalCreateElement = document.createElement;

    document.createElement = function(tagName)
    {
        var element = originalCreateElement(tagName);
        
        if (element.attachEvent)
            element.addEventListener = ieAddEventListener;

        return element;
    }
    
    window.addEventListener = ieAddEventListener;
    document.addEventListener = ieAddEventListener;

    var body = document.body;
    
    if (body)
    {
        if (body.onload)
        {
            var originalBodyOnload = body.onload;

            body.onload = function()
            {
                attachToAll();
                originalBodyOnload();
            };
        }
        else
            body.onload = attachToAll;
    }
    else
        window.addEventListener('load', attachToAll);
}

createIEaddEventListeners();

/*============================================================================*/
// Emulate GM_getValue and GM_setValue in IE
function AddUserData() {
    var userDataDiv = document.createElement("div");
    userDataDiv.id = "userDataDiv";
    document.body.appendChild(userDataDiv);   
    userDataDiv.style.behavior = "url('#default#userData')";
}

function GM_getValue(name, value) {
    userDataDiv.load("GM_storage");
    var ret = userDataDiv.getAttribute(name);
   
    ret = (ret != null) ? ret : value;
    if (ret == "true") { ret = true; }
    if (ret == "false") { ret = false; }
    
    return ret;
}

function GM_setValue(name, value) { 
    userDataDiv.setAttribute(name, value);
    userDataDiv.save("GM_storage");
}
AddUserData();

/*============================================================================*/
// XMLHttpRequest instrumentation/wrapping
var startTracing = function (onnew) {
    var oldActiveXObject = ActiveXObject;
    var oldXMLHttpRequest = XMLHttpRequest;
    
    // create a wrapper object that has the same interfaces as a regular XMLHttpRequest object
    // see http://www.xulplanet.com/references/objref/XMLHttpRequest.html for reference on XHR object
    var ReplacementCtor = function(objectName) {
        var self = this;

        if (objectName != undefined && !objectName.toLowerCase().match(/\.xmlhttp/)) {
            return new oldActiveXObject(objectName);
        }

        var actualXHR;
        if (oldXMLHttpRequest) {
            actualXHR = new oldXMLHttpRequest();
        } else {
            actualXHR = new oldActiveXObject(objectName);
        }
        
        // private callbacks (for UI):
        // onopen, onsend, onsetrequestheader, onupdate, ...
        this.requestHeaders = "";
        this.requestBody = "";
        
        // emulate methods from regular XMLHttpRequest object
        this.open = function(a, b, c, d, e) { 
                self.openMethod = a.toUpperCase();
                self.openURL = b;
                if (self.onopen != null && typeof(self.onopen) == "function") { self.onopen(a,b,c,d,e); } 
                return actualXHR.open(a,b,c,d,e); 
            }
        this.send = function(a) {
                if (self.onsend != null && typeof(this.onsend) == "function") { self.onsend(a); } 
                self.requestBody += a;
                return actualXHR.send(a); 
            }
        this.setRequestHeader = function(a, b) {
                if (self.onsetrequestheader != null && typeof(self.onsetrequestheader) == "function") { self.onsetrequestheader(a, b); } 
                self.requestHeaders += a + ":" + b + "\r\n";
                return actualXHR.setRequestHeader(a, b); 
            }
        this.getRequestHeader = function() {
            return actualXHR.getRequestHeader(); 
        }
        this.getResponseHeader = function(a) { return actualXHR.getResponseHeader(a); }
        this.getAllResponseHeaders = function() { return actualXHR.getAllResponseHeaders(); }
        this.abort = function() { return actualXHR.abort(); }
        this.addEventListener = function(a, b, c) { return actualXHR.addEventListener(a, b, c); }
        this.dispatchEvent = function(e) { return actualXHR.dispatchEvent(e); }
        this.openRequest = function(a, b, c, d, e) { return actualXHR.openRequest(a, b, c, d, e); }
        this.overrideMimeType = function(e) { return actualXHR.overrideMimeType(e); }
        this.removeEventListener = function(a, b, c) { return actualXHR.removeEventListener(a, b, c); }
        
        // copy the values from actualXHR back onto self
        function copyState() {
            // copy properties back from the actual XHR to the wrapper
            try {
                self.readyState = actualXHR.readyState;
            } catch (e) {}
            try {
                self.status = actualXHR.status;
            } catch (e) {}
            try {
                self.responseText = actualXHR.responseText;
            } catch (e) {}
            try {
                self.statusText = actualXHR.statusText;
            } catch (e) {}
            try {
                self.responseXML = actualXHR.responseXML;
            } catch (e) {}
        }
        
        // emulate callbacks from regular XMLHttpRequest object
        actualXHR.onreadystatechange = function() {
            copyState();
            
            try {
                if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); } 
            } catch (e) {}

            // onreadystatechange callback            
            if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { return self.onreadystatechange(); } 
        }
        /*
        actualXHR.onerror = function(e) {
            copyState();

            try {
                if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); } 
            } catch (e) {}

            if (self.onerror != null && typeof(self.onerror) == "function") { 
                return self.onerror(e); 
            } else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { 
                return self.onreadystatechange(); 
            }
        }
        actualXHR.onload = function(e) {
            copyState();

            try {
                if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); } 
            } catch (e) {}

            if (self.onload != null && typeof(self.onload) == "function") { 
                return self.onload(e); 
            } else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { 
                return self.onreadystatechange(); 
            }
        }
        actualXHR.onprogress = function(e) {
            copyState();

            try {
                if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); } 
            } catch (e) {}

            if (self.onprogress != null && typeof(self.onprogress) == "function") { 
                return self.onprogress(e);
            } else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { 
                return self.onreadystatechange(); 
            }
        }
        */
        
        if (onnew && typeof(onnew) == "function") { onnew(this); }
    }
    
    window.ActiveXObject = ReplacementCtor;
    if (oldXMLHttpRequest) {
        window.XMLHttpRequest = ReplacementCtor;
    }
}


/*============================================================================*/
// make a styled link element with an "onclick" callback
var makeActiveLink = function(text, callback, unactive) {
    var link = document.createElement("a");
    
    link.href = "#";
    link.className = unactive ? "XHRDebug_Unactive" : "XHRDebug_Active";
    link.innerHTML = text;
    
    var wrappedCallback = function(e) { e.target = e.target ? e.target : e.srcElement; callback(e); return false; }
    link.addEventListener("click", wrappedCallback, true);
    
    // returns whether the link is "active" after the toggle
    link.toggle = function() {
        if (link.className == "XHRDebug_Unactive") 
        {
            link.className = "XHRDebug_Active";
            return true;
        } 
        else
        {
            link.className = "XHRDebug_Unactive";
            return false;
        }
    }
    
    link.activate = function(active) {
        link.className = active ? "XHRDebug_Active" : "XHRDebug_Unactive";
    }
    
    link.isActive = function() {
        return (link.className == "XHRDebug_Active");
    }
    
    return link;
}

/*============================================================================*/
// add a node into a list, wrapped in a "li" element
var addListItem = function(ul, contentNode) {
    var li = document.createElement("li");
    li.appendChild(contentNode);
    ul.appendChild(li);
}

/*============================================================================*/
// console object
var Console = function() {
    var self = this;
    var capture = true;
    var autoscroll = GM_getValue("ConsoleAutoScrolling", true);
    var showHelp = false;
    
    var uiDiv = document.createElement("div");
    uiDiv.className = "XHRDebug_Console";
    document.body.appendChild(uiDiv);
    
    var uiTitle = document.createElement("div");
    uiTitle.className = "XHRDebug_ConsoleTitle";
    uiDiv.appendChild(uiTitle);
        
    var uiDragHandle = document.createElement("div");
    uiDragHandle.className = "XHRDebug_ConsoleHandle";
    uiDragHandle.innerHTML = "XmlHttpRequest debugging";
    uiTitle.appendChild(uiDragHandle);

    var uiOptions = document.createElement("div");
    uiOptions.className = "XHRDebug_ConsoleOptions";
    uiTitle.appendChild(uiOptions);
    
    var captureActionClick = function() {
        capture = captureActionLink.toggle();
        GM_setValue("ConsoleCapturing", capture);
    }
    
    var autoscrollActionClick = function(e) {
        autoscroll = autoscrollActionLink.toggle();
        GM_setValue("ConsoleAutoScrolling", autoscroll);
    }
    
    var clearActionClick = function(e) {
        Fat.fade_element(uiContent, 30, 500, "#FF0000");
        while (uiTracing.hasChildNodes()) {
            uiTracing.removeChild(uiTracing.firstChild);
        }
    }
    
    var hideActionClick = function() {
        uiDiv.style.display = "none";
        GM_setValue("ConsoleHidden", true);
    }
    
    this.showConsoleClick = function() {
        uiDiv.style.display = "";
        GM_setValue("ConsoleHidden", false);
    }
    
    var exportActionClick = function(e) {
        alert("not implemented");
    }
    
    var helpActionClick = function() {
        var showHelp = helpActionLink.toggle();
        uiHelp.style.display = showHelp ? "" : "none";
        
        if (showHelp) {
            uiContent.style.display = "none";        
        } else {
            if (!GM_getValue("ConsoleMinimized", false)) {
                uiContent.style.display = "";
            }
        }
    }
    
    var minimizeActionClick = function() {
        var minimize = consoleMinimizeOption.toggle();
        uiContent.style.display = (minimize ? "none" : "");
        GM_setValue("ConsoleMinimized", minimize);
        
        if (helpActionLink.isActive()) {
           helpActionClick(); 
        }
    }
    
    var ulNode = document.createElement("ul");
    var captureActionLink = makeActiveLink("capture", captureActionClick);
    
    addListItem(ulNode, captureActionLink);
    var autoscrollActionLink = makeActiveLink("auto-scroll", autoscrollActionClick);
    addListItem(ulNode, autoscrollActionLink);
    addListItem(ulNode, makeActiveLink("clear", clearActionClick, true));
    var helpActionLink = makeActiveLink("help", helpActionClick);
    addListItem(ulNode, helpActionLink);
    var consoleMinimizeOption = makeActiveLink("minimize", minimizeActionClick, true);
    addListItem(ulNode, consoleMinimizeOption);
    //addListItem(ulNode, makeActiveLink("hide", hideActionClick, true));
    
    uiOptions.appendChild(ulNode);

    var uiContent = document.createElement("div");
    uiContent.className = "XHRDebug_ConsoleBody";
    uiDiv.appendChild(uiContent);

    var uiHelp = document.createElement("div");
    uiHelp.className = "XHRDebug_ConsoleBody";
    uiHelp.innerHTML = helpText;
    uiDiv.appendChild(uiHelp);

    var uiTracing = document.createElement("div");
    uiContent.appendChild(uiTracing);

    var uiReplay = document.createElement("div");
    uiContent.appendChild(uiReplay);

    this.traceNode = function(node) {
        uiTracing.appendChild(node);
        Fat.fade_element(node, 30, 2000);
        if (autoscroll) {
            node.scrollIntoView(false);
        }
    }
    
    this.traceXHR = function(xhr) {
        if (!capture) return;
        
        var xhrNode = makeXHRNode(xhr, self);
        xhr.onopen = xhrNode.update;
        xhr.onsend = xhrNode.update;
        xhr.onupdate = xhrNode.update;
        self.traceNode(xhrNode);
    }
    
    this.showReplay = function(displayed) { 
        uiReplay.style.display = displayed ? "" : "none"; 
        uiTracing.style.display = displayed ? "none" : "";
    }
    
    this.insertReplay = function(node) {
        uiReplay.appendChild(node);    
    }
    
    this.clearReplay = function() {
        while (uiReplay.hasChildNodes()) {
            uiReplay.removeChild(uiReplay.firstChild);
        }
    }
    
    this.savePosition = function() {
        GM_setValue("ConsolePosLeft", uiDiv.style.left);
        GM_setValue("ConsolePosTop", uiDiv.style.top);
    }
    
    this.restorePosition = function() {
        // ensure the console is displayed within the viewport
        uiDiv.style.left = GM_getValue("ConsolePosLeft", "10px");
        uiDiv.style.top = GM_getValue("ConsolePosTop", "10px");
    }
    
    if (!GM_getValue("ConsoleCapturing", true)) {
        captureActionClick();
    }
    if (!GM_getValue("ConsoleAutoScrolling", true)) {
        autoscrollActionClick();
    }
    helpActionClick();
    if (GM_getValue("ConsoleMinimized", false)) {
        minimizeActionClick();
    }
    if (GM_getValue("ConsoleHidden", false)) {
        hideActionClick();
    }
    
    this.restorePosition(uiDiv);
    this.showReplay(false);
    

    Drag.init(uiDragHandle, uiDiv);
    uiDiv.onDragEnd = self.savePosition;
}

/*============================================================================*/
var startXHRReplay = function(xhr, uiConsole, xhrNode) {
    // todo: add tooltips
    var methodField = document.createElement("input");
    methodField.type = "text";    
    methodField.maxLength = 5;
    methodField.size = 5;
    methodField.value = xhr.openMethod;
    uiConsole.insertReplay(methodField);

    var urlField = document.createElement("input");
    urlField.type = "text";    
    urlField.size = 50;
    urlField.value = xhr.openURL;
    uiConsole.insertReplay(urlField);
    
    var headersField = document.createElement("textarea");
    headersField.cols = 37;
    headersField.rows = 5;
    headersField.value = xhr.requestHeaders;
    uiConsole.insertReplay(headersField);
  
    var bodyField = document.createElement("textarea");
    bodyField.cols = 37;
    bodyField.rows = 5;
    bodyField.value = xhr.requestBody;
    uiConsole.insertReplay(bodyField);

    var sendClick = function() {
        closeReplay();

        var replayXHR = new XMLHttpRequest();
        replayXHR.replayXHRNode = xhrNode;
        replayXHR.onreadystatechange = xhr.onreadystatechange;
        
        // todo: add username and password
        replayXHR.open(methodField.value, urlField.value, true);
        replayXHR.send(bodyField.value);
    }
    
    var sendLink = makeActiveLink("send", sendClick);
    uiConsole.insertReplay(sendLink);
    
    var closeReplay = function() {
        uiConsole.clearReplay();
        uiConsole.showReplay(false);
    }
    
    var cancelLink = makeActiveLink("cancel", closeReplay);
    uiConsole.insertReplay(cancelLink);
    
    uiConsole.showReplay(true);
}

/*============================================================================*/
// individual entry in the console for an XHR object
var makeXHRNode = function(xhr, uiConsole) {
    var xhrNode = document.createElement("div");
    xhrNode.className = "XHRDebug_XHRNode";
    
    var replayedFromDiv = document.createElement("div"); 
    xhrNode.appendChild(replayedFromDiv);
    
    var openDiv = document.createElement("div"); 
    openDiv.className = "XHRDebug_Request";
    xhrNode.appendChild(openDiv);

    var pickRequestOption = function(e) {
        if (requestReplayOption == e.target) {
            startXHRReplay(xhr, uiConsole, xhrNode);
            return;
        }
        
        requestMinimizeOption.activate(requestMinimizeOption == e.target);
        
        requestHeadersDiv.style.display = (requestHeadersOption == e.target) ? "" : "none";
        requestHeadersOption.activate(requestHeadersOption == e.target);
        
        requestBodyDiv.style.display = (requestBodyOption == e.target) ? "" : "none";
        requestBodyOption.activate(requestBodyOption == e.target);
    }
    
    var requestMinimizeOption = makeActiveLink("minimize", pickRequestOption);
    var requestHeadersOption = makeActiveLink("headers", pickRequestOption, true);
    var requestBodyOption = makeActiveLink("body", pickRequestOption, true);
    var requestReplayOption = makeActiveLink("[edit&amp;replay]", pickRequestOption, true);
    
    var requestOptions = document.createElement("ul");
    requestOptions.className = "XHRDebug_XHROptions";
    addListItem(requestOptions, requestMinimizeOption);
    addListItem(requestOptions, requestHeadersOption);
    addListItem(requestOptions, requestBodyOption);
    addListItem(requestOptions, requestBodyOption);
    //addListItem(requestOptions, requestReplayOption); // todo: edit&replay is broken in IE
    
    xhrNode.appendChild(requestOptions);

    var requestHeadersDiv = document.createElement("div"); 
    requestHeadersDiv.className = "XHRDebug_ResponseBlock";
    xhrNode.appendChild(requestHeadersDiv);

    var requestBodyDiv = document.createElement("div"); 
    requestBodyDiv.className = "XHRDebug_ResponseBlock";
    xhrNode.appendChild(requestBodyDiv);

    var statusDiv = document.createElement("div"); 
    statusDiv.className = "XHRDebug_Status";
    xhrNode.appendChild(statusDiv);
    
  
    var pickResponseOption = function(e) {
        if (callbackReplayOption == e.target) {
            if (xhr.onreadystatechange != null && typeof(xhr.onreadystatechange) == "function") { xhr.onreadystatechange(); } 
            uiConsole.traceNode(makeReplayCallbackNode(xhrNode));
            return;
        }

        responseMinimizeOption.activate(responseMinimizeOption == e.target);

        responseDiv.style.display = (responseOption == e.target) ? "" : "none";
        responseOption.activate(responseOption == e.target);
        
        responseHeadersDiv.style.display = (responseHeadersOption == e.target) ? "" : "none";
        responseHeadersOption.activate(responseHeadersOption == e.target);

        viewCallbackDiv.style.display = (viewOption == e.target) ? "" : "none";
        viewOption.activate(viewOption == e.target);
    }
    
    var responseMinimizeOption = makeActiveLink("minimize", pickResponseOption, true);
    var responseOption = makeActiveLink("response", pickResponseOption, true);
    var responseHeadersOption = makeActiveLink("headers", pickResponseOption, true);

    var viewOption = makeActiveLink("callback", pickResponseOption, true);
    var callbackReplayOption = makeActiveLink("[replay]", pickResponseOption, true);

    var responseOptions = document.createElement("ul");
    responseOptions.className = "XHRDebug_XHROptions";
    addListItem(responseOptions, responseMinimizeOption);
    addListItem(responseOptions, responseHeadersOption);    
    addListItem(responseOptions, responseOption);
    addListItem(responseOptions, viewOption);
    addListItem(responseOptions, callbackReplayOption);    

    xhrNode.appendChild(responseOptions);
    
    var responseDiv = document.createElement("div"); 
    responseDiv.className = "XHRDebug_ResponseBlock";
    xhrNode.appendChild(responseDiv);
   
    var responseHeadersDiv = document.createElement("div"); 
    responseHeadersDiv.className = "XHRDebug_ResponseBlock";
    xhrNode.appendChild(responseHeadersDiv);
     
    var viewCallbackDiv = document.createElement("div"); 
    viewCallbackDiv.className = "XHRDebug_ResponseBlock";
    xhrNode.appendChild(viewCallbackDiv);
    
    var printReadyState = function() {
        if (!xhr.readyState) {
            return "uninitialized";
        }
        
        switch(xhr.readyState) {
            case 0: 
                return "uninitialized";
            case 1:
                return "loading";
            case 2:
                return "loaded";
            case 3:
                return "interactive";
            case 4:
                return "completed";
        }
    }
    
    var formatResponse = function(c) {
	    c = c.replace(/\</g,"&lt;");
	    c = c.replace(/\>/g,"&gt;");
	    c = c.replace(/\"/g,"&quot;");
	    c = c.replace(/\'/g,"&#39;");
	    c = c.replace(/\\/g,"&#92;");
        c = c.replace(/\n/g,"<br />");

	    return c;
    }
    
    xhrNode.update = function() {
        var exportString = ""; 
        if (xhr.replayXHRNode) {
            var replayXHRNode = xhr.replayXHRNode;
            var showReplayedRequest = function() {    
                replayXHRNode.scrollIntoView(true);
                Fat.fade_element(replayXHRNode);                
            }
            var replaydFromLink = makeActiveLink("User replayed request", showReplayedRequest, true);
            replayedFromDiv.appendChild(replaydFromLink);
            xhr.replayXHRNode = null;
        }
        if (xhr.openMethod) {
            openDiv.innerHTML = xhr.openMethod;
            exportString += xhr.openMethod;
        }
        if (xhr.openURL) {
            openDiv.innerHTML += " " + xhr.openURL;
            exportString += " " + xhr.openURL;
        } 
        exportString += "\r\n";
        
        requestHeadersOption.style.display = xhr.requestHeaders ? "" : "none";
        requestHeadersDiv.innerHTML = xhr.requestHeaders ? formatResponse(xhr.requestHeaders) : "<em>&lt;no request headers&gt;</em>";
        exportString += xhr.requestHeaders ? xhr.requestHeaders : "";
        exportString += "\r\n";
        
        requestBodyOption.style.display = xhr.requestBody ? "" : "none";
        requestBodyDiv.innerHTML = xhr.requestBody ? formatResponse(xhr.requestBody) : "<em>&lt;no request body&gt;</em>";
        exportString += xhr.requestBody ? xhr.requestBody : "";
        exportString += "\r\n\r\n";
        

        statusDiv.innerHTML = "Status: " + printReadyState() + " ";
        
        if (xhr.status && xhr.statusText) {
            statusDiv.innerHTML += "(" + xhr.status + " " + xhr.statusText + ") ";
            exportString += xhr.status + " (" + xhr.statusText + ")\r\n\r\n";
        }
        var exportDiv = document.createElement("div");
        exportDiv.style.display = "inline";
        statusDiv.appendChild(exportDiv);
        
        if (xhr.readyState == 4) {
            var responseHeaders = xhr.getAllResponseHeaders();
            if (responseHeaders) {
                responseHeadersDiv.innerHTML = formatResponse(responseHeaders);
                exportString += responseHeaders;
            } else {
                responseHeadersDiv.innerHTML = "<em>&lt;no response headers&gt;</em>";
            }
        }
        exportString += "\r\n";
        
        if (xhr.responseText) {
            responseDiv.innerHTML = formatResponse(xhr.responseText);
            exportString += xhr.responseText;
        } else {
            responseDiv.innerHTML = "<em>&lt;no response&gt;</em>";
        }
        
        if (xhr.onreadystatechange && typeof(xhr.onreadystatechange) == "function") {
            viewCallbackDiv.innerHTML = formatResponse("" + xhr.onreadystatechange);
        } else {
            viewCallbackDiv.innerHTML = "<em>&lt;no callback&gt;</em>";
        }
        
        
        // todo: BUG: export link is broken in IE
        
        
        var exportLink = document.createElement("a");
        exportLink.className = "XHRDebug_Unactive";
        exportLink.appendChild(document.createTextNode("[export]"));
        //exportLink.href = "data:text/plain;charset=utf-8," + encodeURI(exportString);
        exportLink.href = "#";
        exportLink.onclick = exportAction;
        exportDiv.appendChild(exportLink);
        
        function exportAction(e) {
            // todo
            //window.open("", "_blank", "width=100,left=50, height=,resizable,scrollbars=yes");
            alert(exportString);            
            return false;
        }
    }
    
    xhrNode.update();
    pickRequestOption( {target: requestMinimizeOption} );
    pickResponseOption( {target: responseMinimizeOption} );
    
    return xhrNode;
}

var makeReplayCallbackNode = function(xhrNode) {
    var replayNode = document.createElement("div");
    replayNode.className = "XHRDebug_XHRNode";
    
    var showReplayedCallback = function() {
        xhrNode.scrollIntoView(true);
        Fat.fade_element(xhrNode);
    }
    
    replayNode.appendChild(makeActiveLink("User replayed callback", showReplayedCallback, true));
    
    return replayNode;
}


/*============================================================================*/
var AddGlobalStyle = function(css) 
{
    document.createStyleSheet().cssText=css;
}
var css = 
".XHRDebug_Console { font-size: 12px; position: absolute; width: 340px;  background-color: #FFF; -moz-border-radius: 7px; color: black; z-index: 999999999; }" +
".XHRDebug_Console h1 { font-size: 15px; }" +
".XHRDebug_Console h2 { font-size: 12px; text-decoration: underline; }" +
".XHRDebug_Console dt { font-weight: bold; }" +
".XHRDebug_Console a { font-size: 12px; color: black; }" +
".XHRDebug_ConsoleTitle { -moz-border-radius: 7px 7px 0px 0px; border: 2px; border-color: #000; border-style: solid; background-color: #c3d9ff; color: #003ce6; padding: 1px 0px 0px 2px; }" +
".XHRDebug_ConsoleHandle { font-weight: bold; -moz-border-radius: 7px 7px 0px 0px; background-color: }" +
".XHRDebug_ConsoleOptions { margin-bottom: 1px; }" +
".XHRDebug_ConsoleOptions ul, .XHRDebug_ConsoleOptions li { margin: 0; padding: 0; display: inline; }" +
".XHRDebug_Active { color: black; font-weight: bold; margin-right: 3px; text-decoration: none; cursor: hand; }" +
".XHRDebug_Unactive { color: black; margin-right: 3px; text-decoration: none; cursor: hand; }" +
".XHRDebug_ConsoleBody { background-color: #e8eef7; height: 400px; overflow: auto; padding: 5px; -moz-border-radius: 0px 0px 7px 7px; border: 2px; border-color: #000; border-style: none solid solid solid;}" +
".XHRDebug_XHRNode { background-color: white; border: 1px; border-color: #000; border-style: solid; margin: 0px 0px 4px 0px; padding: 2px; }" +
".XHRDebug_Request { overflow: hidden; }" +
".XHRDebug_Status { margin-bottom: 7px; }" +
".XHRDebug_XHROptions { margin-bottom: 1px; }" +
".XHRDebug_XHROptions, .XHRDebug_XHROptions li { margin: 0; padding: 0; display: inline; }" +
".XHRDebug_ResponseBlock { padding: 2px; border: 1px; border-color: #CCC; border-style: solid; overflow: hidden; }" +
"";

var helpText = 
"<h1>XMLHttpRequest debugging help</h1>" +
"<h2>Console options and actions</h2>" +
"<dl>" +
"<dt>capture</dt><dd>Turn capturing on/off.</dd>" +
"<dt>auto-scroll</dt><dd>Toggle auto-scrolling. When on, the capture window will automatically scroll down as new events are catpured.</dd>" +
"<dt>clear</dt><dd>Clear the capture history.</dd>" +
"<dt>help</dt><dd>Show/hide this page.</dd>" +
"<dt>minimize</dt><dd>Reduce the footprint of the XmlHttpRequest debugging window.</dd>" +
"<dt>hide</dt><dd>Completely hide the XmlHttpRequest debugging window. The window can be brought back with the 'Tools' -> 'User scripts commands' -> 'Show XmlHttpRequest console' menu item.</dd>" +
"</dl>" +
"<h2>History options and actions</h2>" +
"<dl>" +
"<dt>headers (in the request)</dt><dd>Shows the request headers, if any where set.</dd>" +
"<dt>body</dt><dd>Show the request body, if any was set.</dd>" +
"<dt>edit&amp;replay (the request)</dt><dd>Lets you create a new XMLHttpRequest request based on the selected one.</dd>" +
"<dt>export</dt><dd>Opens a new window with the text for the request and the response.</dd>" +
"<dt>headers (in the response)</dt><dd>Shows the response headers.</dd>" +
"<dt>response</dt><dd>Shows the response body.</dd>" +
"<dt>callback</dt><dd>Shows the source code for the XMLHttpRequest callback that was set.</dd>" +
"<dt>replay (the callback)</dt><dd>Calls the callback again.</dd>" +
"</dl>" +
"<h1>About</h1>" +
"<p>'XMLHttpRequest Debugging' bookmarklet for IE. Version 1.0</p>" +
"<p>Based on the 'XMLHttpRequest Debugging' Greasemonkey user script (Version 1.2).</p>" +
"<p>Written by <a href='&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#106;&#117;&#108;&#105;&#101;&#110;&#46;&#99;&#111;&#117;&#118;&#114;&#101;&#117;&#114;&#64;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;'>Julien Couvreur</a>.</p>" +
"<p>More info and other javascript goodies on <a href='http://blog.monstuff.com/archives/cat_greasemonkey.html'>my site</a>.</p><br />" +
"";


/*============================================================================*/    
/**************************************************
 * dom-drag.js
 * 09.25.2001
 * www.youngpup.net
 **************************************************
 * 10.28.2001 - fixed minor bug where events
 * sometimes fired off the handle, not the root.
 **************************************************/

var Drag = {
    obj : null,

    init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper)
    {
        o.onmousedown      = Drag.start;

        o.hmode            = bSwapHorzRef ? false : true ;
        o.vmode            = bSwapVertRef ? false : true ;

        o.root = oRoot && oRoot != null ? oRoot : o ;

        if (o.hmode  && isNaN(parseInt(o.root.style.left  ))) o.root.style.left   = "0px";
        if (o.vmode  && isNaN(parseInt(o.root.style.top   ))) o.root.style.top    = "0px";
        if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right  = "0px";
        if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px";

        o.minX    = typeof minX != 'undefined' ? minX : null;
        o.minY    = typeof minY != 'undefined' ? minY : null;
        o.maxX    = typeof maxX != 'undefined' ? maxX : null;
        o.maxY    = typeof maxY != 'undefined' ? maxY : null;

        o.xMapper = fXMapper ? fXMapper : null;
        o.yMapper = fYMapper ? fYMapper : null;

        o.root.onDragStart    = new Function();
        o.root.onDragEnd    = new Function();
        o.root.onDrag        = new Function();
    },

    start : function(e)
    {
        var o = Drag.obj = this;
        e = Drag.fixE(e);
        var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
        var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
        o.root.onDragStart(x, y);

        o.lastMouseX    = e.clientX;
        o.lastMouseY    = e.clientY;

        if (o.hmode) {
            if (o.minX != null)    o.minMouseX    = e.clientX - x + o.minX;
            if (o.maxX != null)    o.maxMouseX    = o.minMouseX + o.maxX - o.minX;
        } else {
            if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x;
            if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;
        }

        if (o.vmode) {
            if (o.minY != null)    o.minMouseY    = e.clientY - y + o.minY;
            if (o.maxY != null)    o.maxMouseY    = o.minMouseY + o.maxY - o.minY;
        } else {
            if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y;
            if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;
        }

        document.onmousemove    = Drag.drag;
        document.onmouseup        = Drag.end;

        return false;
    },

    drag : function(e)
    {
        e = Drag.fixE(e);
        var o = Drag.obj;

        var ey    = e.clientY;
        var ex    = e.clientX;
        var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
        var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
        var nx, ny;

        if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
        if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
        if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
        if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);

        nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
        ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));

        if (o.xMapper)        nx = o.xMapper(y)
        else if (o.yMapper)    ny = o.yMapper(x)

        Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
        Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
        Drag.obj.lastMouseX    = ex;
        Drag.obj.lastMouseY    = ey;

        Drag.obj.root.onDrag(nx, ny);
        return false;
    },

    end : function()
    {
        document.onmousemove = null;
        document.onmouseup   = null;
        Drag.obj.root.onDragEnd(    parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]), 
                                    parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"]));
        Drag.obj = null;
    },

    fixE : function(e)
    {
        if (typeof e == 'undefined') e = window.event;
        if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
        if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
        return e;
    }
};


/*============================================================================*/    
// @name            The Fade Anything Technique (revisited)
//                  Based on http://www.axentric.com/aside/fat/
// @version         1.0
// @author          Julien Couvreur
// @original author Adam Michela

var Fat = {
	make_hex : function (r,g,b) 
	{
		r = r.toString(16); if (r.length == 1) r = '0' + r;
		g = g.toString(16); if (g.length == 1) g = '0' + g;
		b = b.toString(16); if (b.length == 1) b = '0' + b;
		return "#" + r + g + b;
	},

	fade_element : function (element, fps, duration, from, to) 
	{
	// return; // todo: there is a problem with set_bgcolor
	
		if (!fps) fps = 30;
		if (!duration) duration = 2000;
		if (!from || from=="#") from = "#FFFF33";
		if (!to) to = this.get_bgcolor(element);
		
		var frames = Math.round(fps * (duration / 1000));
		var interval = duration / frames;
		var delay = interval;
		var frame = 0;
		
		if (from.length < 7) from += from.substr(1,3);
		if (to.length < 7) to += to.substr(1,3);
		
		var rf = parseInt(from.substr(1,2),16);
		var gf = parseInt(from.substr(3,2),16);
		var bf = parseInt(from.substr(5,2),16);
		var rt = parseInt(to.substr(1,2),16);
		var gt = parseInt(to.substr(3,2),16);
		var bt = parseInt(to.substr(5,2),16);
						
		var r,g,b,h;
		while (frame < frames)
		{
			r = Math.floor(rf * ((frames-frame)/frames) + rt * (frame/frames));
			g = Math.floor(gf * ((frames-frame)/frames) + gt * (frame/frames));
			b = Math.floor(bf * ((frames-frame)/frames) + bt * (frame/frames));
			h = this.make_hex(r,g,b);
				    
			setTimeout(this.makeTimer(element, h), delay);

			frame++;
			delay = interval * frame; 
		}
		setTimeout(this.makeTimer(element, to), delay);
	},
	
	makeTimer : function(element, color) {
	    return function() { Fat.set_bgcolor(element, color); }
	},
	
	set_bgcolor : function (element, c)
	{
		element.style.backgroundColor = c;
	},
	
	get_bgcolor : function (element)
	{
		while(element)
		{
			var c;
		
			if (window.getComputedStyle) c = window.getComputedStyle(element,null).getPropertyValue("background-color");
			if (element.currentStyle) { c = element.currentStyle.backgroundColor; }
			if ((c != "" && c != "transparent") || element.tagName == "BODY") { break; }
			element = element.parentNode;
		}

		if (c == undefined || c == "" || c == "transparent") c = "#FFFFFF";
		c = this.fix_color(c);
		var rgb = c.match(/rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/);
		if (rgb) c = this.make_hex(parseInt(rgb[1]),parseInt(rgb[2]),parseInt(rgb[3]));
		
		return c;
	}, 
	
	fix_color : function (string) 
	{ 
	    if (!string.match(/#/)) {
	        // todo: we should do better here (using a table of known colors, such as "white", "beige",...)
	        return "#FFFFFF";
	    } else {
	        return string;
	    }
	}
}

AddGlobalStyle(css);

var console = new Console();
startTracing(console.traceXHR);
