issue #41 - add support for XHRs on Network Panel

https://github.com/phonegap/weinre/issues/41
diff --git a/weinre.web/modules/weinre/client/InspectorFrontendHostImpl.coffee b/weinre.web/modules/weinre/client/InspectorFrontendHostImpl.coffee
index 0c71e63..b060469 100644
--- a/weinre.web/modules/weinre/client/InspectorFrontendHostImpl.coffee
+++ b/weinre.web/modules/weinre/client/InspectorFrontendHostImpl.coffee
@@ -26,7 +26,8 @@
 
     #---------------------------------------------------------------------------
     hiddenPanels: ->
-        "audits,profiles,network"
+        # "audits,profiles,network"
+        "audits,profiles"
 
     #---------------------------------------------------------------------------
     platform: ->
diff --git a/weinre.web/modules/weinre/common/Ex.coffee b/weinre.web/modules/weinre/common/Ex.coffee
index 3de56c7..4062188 100644
--- a/weinre.web/modules/weinre/common/Ex.coffee
+++ b/weinre.web/modules/weinre/common/Ex.coffee
@@ -11,6 +11,15 @@
 #-------------------------------------------------------------------------------
 module.exports = class Ex
 
+    #---------------------------------------------------------------------------
+    @catching: (func) ->
+        try
+            func.call(this)
+        catch e
+            console.log "runtime error: #{e}"
+            StackTrace.dump arguments
+
+    #---------------------------------------------------------------------------
     constructor: (args, message) ->
         if not args or not args.callee
             throw Ex(arguments, "first parameter must be an Arguments object")
diff --git a/weinre.web/modules/weinre/common/WebSocketXhr.coffee b/weinre.web/modules/weinre/common/WebSocketXhr.coffee
index f37be52..00c741e 100644
--- a/weinre.web/modules/weinre/common/WebSocketXhr.coffee
+++ b/weinre.web/modules/weinre/common/WebSocketXhr.coffee
@@ -192,7 +192,9 @@
             xhr.open method, url, true
 
         xhr.setRequestHeader "Content-Type", "text/plain"
-        xhr.send data
+
+        HookLib.ignoreHooks ->
+            xhr.send data
 
 #-------------------------------------------------------------------------------
 _xhrEventHandler = (event) ->
diff --git a/weinre.web/modules/weinre/target/ElementHighlighterDivs2.coffee b/weinre.web/modules/weinre/target/ElementHighlighterDivs2.coffee
index 8b70935..8185160 100644
--- a/weinre.web/modules/weinre/target/ElementHighlighterDivs2.coffee
+++ b/weinre.web/modules/weinre/target/ElementHighlighterDivs2.coffee
@@ -62,7 +62,6 @@
 
     #---------------------------------------------------------------------------
     redraw: (metrics) ->
-        console.log(JSON.stringify(metrics, null, 4))
 
         @hElement1.style.top               = px metrics.y
         @hElement1.style.left              = px metrics.x
diff --git a/weinre.web/modules/weinre/target/HookSites.coffee b/weinre.web/modules/weinre/target/HookSites.coffee
index ef37e98..13dedb1 100644
--- a/weinre.web/modules/weinre/target/HookSites.coffee
+++ b/weinre.web/modules/weinre/target/HookSites.coffee
@@ -19,6 +19,7 @@
 HookSites.window_addEventListener         = HookLib.addHookSite window, "addEventListener"
 HookSites.Node_addEventListener           = HookLib.addHookSite Node.prototype, "addEventListener"
 HookSites.XMLHttpRequest_open             = HookLib.addHookSite XMLHttpRequest.prototype, "open"
+HookSites.XMLHttpRequest_send             = HookLib.addHookSite XMLHttpRequest.prototype, "send"
 HookSites.XMLHttpRequest_addEventListener = HookLib.addHookSite XMLHttpRequest.prototype, "addEventListener"
 
 if window.openDatabase
diff --git a/weinre.web/modules/weinre/target/NetworkRequest.coffee b/weinre.web/modules/weinre/target/NetworkRequest.coffee
new file mode 100644
index 0000000..64849d1
--- /dev/null
+++ b/weinre.web/modules/weinre/target/NetworkRequest.coffee
@@ -0,0 +1,176 @@
+
+#---------------------------------------------------------------------------------
+# weinre is available under *either* the terms of the modified BSD license *or* the
+# MIT License (2008). See http:#opensource.org/licenses/alphabetical for full text.
+#
+# Copyright (c) 2011 IBM Corporation
+#---------------------------------------------------------------------------------
+
+StackTrace  = require('../common/StackTrace')
+IDGenerator = require('../common/IDGenerator')
+HookLib     = require('../common/HookLib')
+Weinre      = require('../common/Weinre')
+Ex          = require('../common/Ex')
+HookSites   = require('./HookSites')
+
+Loader =
+    url:      window.location.href
+    frameId:  0
+    loaderId: 0
+
+#-------------------------------------------------------------------------------
+module.exports = class NetworkRequest
+
+    #---------------------------------------------------------------------------
+    constructor: (@xhr, @id, @method, @url, @stackTrace) ->
+
+    #---------------------------------------------------------------------------
+    handleSend: (data) ->
+        Weinre.wi.NetworkNotify.identifierForInitialRequest(@id, @url, Loader, @stackTrace)
+
+        time             = Date.now() / 1000.0
+        request          = getRequest(@url, @method, @xhr, data)
+        redirectResponse = {isNull: true}
+
+        Weinre.wi.NetworkNotify.willSendRequest(@id, time, request, redirectResponse)
+
+    #---------------------------------------------------------------------------
+    handleHeadersReceived: ->
+        time     = Date.now() / 1000.0
+        response = getResponse(@xhr)
+        Weinre.wi.NetworkNotify.didReceiveResponse(@id, time, "XHR", response)
+
+    #---------------------------------------------------------------------------
+    handleLoading: ->
+
+    #---------------------------------------------------------------------------
+    handleDone: ->
+        sourceString = @xhr.responseText
+        Weinre.wi.NetworkNotify.setInitialContent(@id, sourceString, "XHR")
+
+        time       = Date.now() / 1000.0
+        status     = @xhr.status
+        status     = 200 if status == 0
+        statusText = @xhr.statusText
+
+        success = status >= 200 and status < 300
+
+        if success
+            Weinre.wi.NetworkNotify.didFinishLoading(@id, time)
+        else
+            description = "#{status} - #{statusText}"
+            Weinre.wi.NetworkNotify.didFailLoading(@id, time, description)
+
+    #---------------------------------------------------------------------------
+    @installNativeHooks: ->
+
+        #-----------------------------------------------------------------------
+        HookSites.XMLHttpRequest_open.addHooks
+
+            before:  (receiver, args) ->
+                xhr = receiver
+
+                method = args[0]
+                url    = args[1]
+                id     = IDGenerator.next()
+
+                rawStackTrace = new StackTrace(args).trace.slice(1)
+
+                stackTrace = []
+                for frame in rawStackTrace
+                    stackTrace.push({functionName: frame})
+
+                xhr.__weinreNetworkRequest__ = new NetworkRequest(xhr, id, method, url, stackTrace)
+
+                HookLib.ignoreHooks ->
+                    xhr.addEventListener "readystatechange", getXhrEventHandler(xhr), false
+
+        #-----------------------------------------------------------------------
+        HookSites.XMLHttpRequest_send.addHooks
+
+            before:  (receiver, args) ->
+                xhr  = receiver
+                data = args[0]
+                nr   = xhr.__weinreNetworkRequest__
+                return unless nr
+
+                nr.handleSend(data)
+
+#-------------------------------------------------------------------------------
+getRequest = (url, method, xhr, data) ->
+
+    return {
+        url:              url
+        httpMethod:       method
+        httpHeaderFields: {}
+        requestFormData:  getFormData(url, data)
+    }
+
+#-------------------------------------------------------------------------------
+getResponse = (xhr) ->
+    contentType = xhr.getResponseHeader("Content-Type")
+
+    [contentType, encoding] = splitContentType(contentType)
+
+    headers = getHeaders(xhr)
+
+    return {
+        mimeType: contentType
+        expectedContentLength: contentType
+        textEncodingName:      encoding
+        httpStatusCode:        xhr.status
+        httpStatusText:        xhr.statusText
+        httpHeaderFields:      headers
+        connectionReused:      false
+        connectionID:          0
+        wasCached:             false
+    }
+
+#-------------------------------------------------------------------------------
+getHeaders = (xhr) ->
+    string = xhr.getAllResponseHeaders()
+    lines = string.split('\r\n')
+
+    result = {}
+    for line in lines
+        line = trim(line)
+        break if line == ""
+
+        [key, val] = line.split(':', 2)
+        result[trim(key)] = trim(val)
+
+    result
+
+#-------------------------------------------------------------------------------
+trim = (string) ->
+    string.replace(/^\s+|\s+$/g, '')
+
+#-------------------------------------------------------------------------------
+getFormData = (url, data) ->
+    return data if data
+
+    pattern = /.*?\?(.*?)(#.*)?$/
+    match = url.match(pattern)
+    return match[1] if match
+
+    return ""
+
+#-------------------------------------------------------------------------------
+splitContentType = (contentType) ->
+    pattern = /\s*(.*?)\s*(;\s*(.*))?\s*$/
+    match = contentType.match(pattern)
+    return [contentType, ""] unless match
+
+    return [match[1], match[3]]
+
+#-------------------------------------------------------------------------------
+getXhrEventHandler = (xhr) ->
+    ->
+        nr = xhr.__weinreNetworkRequest__
+        return unless nr
+
+        switch xhr.readyState
+            when 2 then nr.handleHeadersReceived()
+            when 3 then nr.handleLoading()
+            when 4 then nr.handleDone()
+
diff --git a/weinre.web/modules/weinre/target/Target.coffee b/weinre.web/modules/weinre/target/Target.coffee
index 8dbceb6..df91a94 100644
--- a/weinre.web/modules/weinre/target/Target.coffee
+++ b/weinre.web/modules/weinre/target/Target.coffee
@@ -19,6 +19,7 @@
 ElementHighlighter            = require('./ElementHighlighter')
 ExceptionalCallbacks          = require('./ExceptionalCallbacks')
 InjectedScriptHostImpl        = require('./InjectedScriptHostImpl')
+NetworkRequest                = require('./NetworkRequest')
 WeinreTargetEventsImpl        = require('./WeinreTargetEventsImpl')
 WeinreExtraClientCommandsImpl = require('./WeinreExtraClientCommandsImpl')
 WiConsoleImpl                 = require('./WiConsoleImpl')
@@ -40,8 +41,6 @@
         Weinre.target = new Target()
         Weinre.target.initialize()
 
-        ExceptionalCallbacks.addHooks()
-
     #----------------------------------------------------------------------------
     setWeinreServerURLFromScriptSrc: (element) ->
         return if window.WeinreServerURL
@@ -143,6 +142,7 @@
         Weinre.wi.DatabaseNotify         = messageDispatcher.createProxy("DatabaseNotify")
         Weinre.wi.InspectorNotify        = messageDispatcher.createProxy("InspectorNotify")
         Weinre.wi.TimelineNotify         = messageDispatcher.createProxy("TimelineNotify")
+        Weinre.wi.NetworkNotify          = messageDispatcher.createProxy("NetworkNotify")
         Weinre.WeinreTargetCommands      = messageDispatcher.createProxy("WeinreTargetCommands")
         Weinre.WeinreExtraTargetEvents   = messageDispatcher.createProxy("WeinreExtraTargetEvents")
 
@@ -155,6 +155,9 @@
             Target.handleError e
         ), false
 
+        ExceptionalCallbacks.addHooks()
+        NetworkRequest.installNativeHooks()
+
     #---------------------------------------------------------------------------
     @handleError: (event) ->
         filename = event.filename or "[unknown filename]"