home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 June / PersonalComputerWorld-June2009-CoverdiscCD.iso / Software / Freeware / Firebug 1.3.3 / firebug-1.3.3-fx.xpi / content / firebug / spy.js < prev    next >
Encoding:
JavaScript  |  2009-02-19  |  19.1 KB  |  666 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10. const nsIHttpChannel = Ci.nsIHttpChannel;
  11. const nsIUploadChannel = Ci.nsIUploadChannel;
  12. const nsIRequest = Ci.nsIRequest;
  13. const nsIXMLHttpRequest = Ci.nsIXMLHttpRequest;
  14. const nsIWebProgress = Ci.nsIWebProgress;
  15.  
  16. const observerService = CCSV("@joehewitt.com/firebug-http-observer;1", "nsIObserverService");
  17.  
  18. // ************************************************************************************************
  19.  
  20. var contexts = [];
  21. const httpObserver =
  22. {
  23.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  24.     // nsIObserver
  25.  
  26.     observe: function(request, topic, data)
  27.     {
  28.         try
  29.         {
  30.             // If a progress listener is set for the XHR, the loadFlags doesn't have
  31.             // nsIRequest.LOAD_BACKGROUND flag set. So, don't use it as a condition for displaying
  32.             // the XHR in Firebug console (Issue #1229).
  33.             if ((topic == "http-on-modify-request") || (topic == "http-on-examine-response"))
  34.             {
  35.                 request = QI(request, nsIHttpChannel);
  36.                 if (request.notificationCallbacks)
  37.                 {
  38.                     try
  39.                     {
  40.                         var xhrRequest = request.notificationCallbacks.getInterface(nsIXMLHttpRequest);
  41.                     }
  42.                     catch (e)
  43.                     {
  44.                         if (e.name == "NS_NOINTERFACE")
  45.                         {
  46.                         }
  47.                     }
  48.                     if (xhrRequest && request.loadGroup)
  49.                     {
  50.                         var win = QI(request.loadGroup.groupObserver, nsIWebProgress).DOMWindow;
  51.                         for( var i = 0; i < contexts.length; ++i )
  52.                         {
  53.                             if (contexts[i].win == win)
  54.                             {
  55.                                 if (topic == "http-on-modify-request")
  56.                                   requestStarted(request, xhrRequest, contexts[i].context, request.requestMethod, request.URI.asciiSpec);
  57.                                 else if (topic == "http-on-examine-response")
  58.                                   requestStopped(request, xhrRequest, contexts[i].context, request.requestMethod, request.URI.asciiSpec);
  59.  
  60.                                 return;
  61.                             }
  62.                         }
  63.                     }
  64.                 }
  65.             }
  66.         }
  67.         catch(exc)
  68.         {
  69.         }
  70.     }
  71. };
  72.  
  73. // List of listeners that can be registerd by other FB extensions.
  74. // See Firebug.Spy.addListener and Firebug.Spy.removeListener.
  75. var listeners = [];
  76.  
  77. // ************************************************************************************************
  78.  
  79. Firebug.Spy = extend(Firebug.Module,
  80. {
  81.     skipSpy: function(win)
  82.     {
  83.         var uri = win.location.href; // don't attach spy to chrome
  84.         if (uri &&  (uri.indexOf("about:") == 0 || uri.indexOf("chrome:") == 0))
  85.                 return true;
  86.     },
  87.  
  88.     attachSpy: function(context, win)
  89.     {
  90.         if (win)
  91.         {
  92.             if (Firebug.Spy.skipSpy(win))
  93.                 return;
  94.  
  95.             for( var i = 0; i < contexts.length; ++i )
  96.             {
  97.                 if ( (contexts[i].context == context) && (contexts[i].win == win) )
  98.                     return;
  99.             }
  100.             if ( contexts.length == 0 )
  101.                 observerService.addObserver(httpObserver, "firebug-http-event", false);
  102.             contexts.push({ context: context, win: win });
  103.         }
  104.     },
  105.  
  106.     detachSpy: function(context, win)
  107.     {
  108.         for( var i = 0; i < contexts.length; ++i )
  109.         {
  110.             if ( (contexts[i].context == context) )
  111.             {
  112.                 if (win && (contexts[i].win != win) )
  113.                     continue;
  114.                 contexts.splice(i, 1);
  115.                 if ( contexts.length == 0 )
  116.                     observerService.removeObserver(httpObserver, "firebug-http-event");
  117.                 return;
  118.             }
  119.         }
  120.     },
  121.  
  122.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  123.     // extends Module
  124.  
  125.     initContext: function(context)
  126.     {
  127.         context.spies = [];
  128.  
  129.         if (Firebug.showXMLHttpRequests  && Firebug.Console.isEnabled(context))
  130.             this.attachSpy(context, context.window);
  131.     },
  132.  
  133.     destroyContext: function(context)
  134.     {
  135.         // For any spies that are in progress, remove our listeners so that they don't leak
  136.         this.detachSpy(context, false);
  137.         delete context.spies;
  138.     },
  139.  
  140.     watchWindow: function(context, win)
  141.     {
  142.         if (Firebug.showXMLHttpRequests && Firebug.Console.isEnabled(context))
  143.             this.attachSpy(context, win);
  144.     },
  145.  
  146.     unwatchWindow: function(context, win)
  147.     {
  148.         try {
  149.             // This make sure that the existing context is properly removed from "contexts" array.
  150.             this.detachSpy(context, win);
  151.         } catch (ex) {
  152.             // Get exceptions here sometimes, so let's just ignore them
  153.             // since the window is going away anyhow
  154.             ERROR(ex);
  155.         }
  156.     },
  157.  
  158.     updateOption: function(name, value)
  159.     {
  160.         if (name == "showXMLHttpRequests")  // XXXjjb Honza, if Console.isEnabled(context) false, then this can't be called, but somehow seems not correct
  161.         {
  162.             var tach = value ? this.attachSpy : this.detachSpy;
  163.             for (var i = 0; i < TabWatcher.contexts.length; ++i)
  164.             {
  165.                 var context = TabWatcher.contexts[i];
  166.                 iterateWindows(context.window, function(win)
  167.                 {
  168.                     tach.apply(this, [context, win]);
  169.                 });
  170.             }
  171.         }
  172.     },
  173.  
  174.     addListener: function(listener)
  175.     {
  176.         listeners.push(listener);
  177.     },
  178.  
  179.     removeListener: function(listener)
  180.     {
  181.         remove(listeners, listener);
  182.     }
  183. });
  184.  
  185. // ************************************************************************************************
  186.  
  187. Firebug.Spy.XHR = domplate(Firebug.Rep,
  188. {
  189.     tag:
  190.         DIV({class: "spyHead", _repObject: "$object"},
  191.             TABLE({cellpadding: 0, cellspacing: 0},
  192.                 TBODY(
  193.                     TR({class: "spyRow"},
  194.                         TD({class: "spyTitleCol spyCol", onclick: "$onToggleBody"},
  195.                             DIV({class: "spyTitle"},
  196.                                 "$object|getCaption"
  197.                             ),
  198.                             DIV({class: "spyFullTitle spyTitle"},
  199.                                 "$object|getFullUri"
  200.                             )
  201.                         ),
  202.                         TD({class: "spyCol"},
  203.                             DIV({class: "spyStatus"}, "$object|getStatus")
  204.                         ),
  205.                         TD({class: "spyCol"},
  206.                             IMG({class: "spyIcon", src: "blank.gif"})
  207.                         ),
  208.                         TD({class: "spyCol"},
  209.                             SPAN({class: "spyTime"})
  210.                         ),
  211.                         TD({class: "spyCol"},
  212.                             TAG(FirebugReps.SourceLink.tag, {object: "$object.sourceLink"})
  213.                         )
  214.                     )
  215.                 )
  216.             )
  217.         ),
  218.  
  219.     getCaption: function(spy)
  220.     {
  221.         return spy.method.toUpperCase() + " " + cropString(spy.getURL(), 100);
  222.     },
  223.  
  224.     getFullUri: function(spy)
  225.     {
  226.         return spy.getURL();
  227.     },
  228.  
  229.     getStatus: function(spy)
  230.     {
  231.         if (spy.statusCode && spy.statusText)
  232.             return spy.statusCode + " " + spy.statusText;
  233.  
  234.         return "";
  235.     },
  236.  
  237.     onToggleBody: function(event)
  238.     {
  239.         var target = event.currentTarget;
  240.         var logRow = getAncestorByClass(target, "logRow-spy");
  241.  
  242.         if (isLeftClick(event))
  243.         {
  244.             toggleClass(logRow, "opened");
  245.  
  246.             if (hasClass(logRow, "opened"))
  247.             {
  248.                 var spy = getChildByClass(logRow, "spyHead").repObject;
  249.                 updateHttpSpyInfo(spy);
  250.             }
  251.         }
  252.     },
  253.  
  254.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  255.  
  256.     copyURL: function(spy)
  257.     {
  258.         copyToClipboard(spy.getURL());
  259.     },
  260.  
  261.     copyParams: function(spy)
  262.     {
  263.         var text = spy.postText;
  264.         if (!text)
  265.             return;
  266.  
  267.         var url = reEncodeURL(spy, text);
  268.         copyToClipboard(url);
  269.     },
  270.  
  271.     copyResponse: function(spy)
  272.     {
  273.         copyToClipboard(spy.responseText);
  274.     },
  275.  
  276.     openInTab: function(spy)
  277.     {
  278.         openNewTab(spy.getURL());
  279.     },
  280.  
  281.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  282.  
  283.     supportsObject: function(object)
  284.     {
  285.         return object instanceof XMLHttpRequestSpy;
  286.     },
  287.  
  288.     browseObject: function(spy, context)
  289.     {
  290.         var url = spy.getURL();
  291.         openNewTab(url);
  292.         return true;
  293.     },
  294.  
  295.     getRealObject: function(spy, context)
  296.     {
  297.         return spy.xhrRequest;
  298.     },
  299.  
  300.     getContextMenuItems: function(spy)
  301.     {
  302.         var items = [
  303.             {label: "CopyLocation", command: bindFixed(this.copyURL, this, spy) }
  304.         ];
  305.  
  306.         if (spy.postText)
  307.         {
  308.             items.push(
  309.                 {label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, spy) }
  310.             );
  311.         }
  312.  
  313.         items.push(
  314.             {label: "CopyResponse", command: bindFixed(this.copyResponse, this, spy) },
  315.             "-",
  316.             {label: "OpenInTab", command: bindFixed(this.openInTab, this, spy) }
  317.         );
  318.  
  319.         return items;
  320.     }
  321. });
  322.  
  323. // ************************************************************************************************
  324.  
  325. top.XMLHttpRequestSpy = function(request, xhrRequest, context)
  326. {
  327.     this.request = request;
  328.     this.xhrRequest = xhrRequest;
  329.     this.context = context;
  330.     this.responseText = null;
  331.  
  332.     this.onStoreResponse = function(win, request, lines)
  333.     {
  334.         if (request == this.request)
  335.             this.responseText = lines ? lines.join("\n") : "";
  336.     };
  337. };
  338.  
  339. top.XMLHttpRequestSpy.prototype =
  340. {
  341.     attach: function()
  342.     {
  343.         var spy = this;
  344.         this.onReadyStateChange = function(event) { onHTTPSpyReadyStateChange(spy, event); };
  345.         this.onLoad = function() { onHTTPSpyLoad(spy); };
  346.         this.onError = function() { onHTTPSpyError(spy); };
  347.  
  348.         this.onreadystatechange = this.xhrRequest.onreadystatechange;
  349.  
  350.         this.xhrRequest.onreadystatechange = this.onReadyStateChange;
  351.         this.xhrRequest.addEventListener("load", this.onLoad, true);
  352.         this.xhrRequest.addEventListener("error", this.onError, true);
  353.  
  354.         // Use tabCache to get XHR response. Notice that the tabCache isn't 
  355.         // supported till Firefox 3.0.4
  356.         if (this.context.sourceCache.addListener)
  357.             this.context.sourceCache.addListener(this);
  358.     },
  359.  
  360.     detach: function()
  361.     {
  362.         this.xhrRequest.onreadystatechange = this.onreadystatechange;
  363.         try { this.xhrRequest.removeEventListener("load", this.onLoad, true); } catch (e) {}
  364.         try { this.xhrRequest.removeEventListener("error", this.onError, true); } catch (e) {}
  365.  
  366.         this.onreadystatechange = null;
  367.         this.onLoad = null;
  368.         this.onError = null;
  369.  
  370.         if (this.context.sourceCache.removeListener)
  371.             this.context.sourceCache.removeListener(this);
  372.     },
  373.  
  374.     getURL: function()
  375.     {
  376.         return this.xhrRequest.channel ? this.xhrRequest.channel.name : this.href;
  377.     }
  378. };
  379.  
  380. Firebug.XHRSpyListener =
  381. {
  382.     onStart: function(context, spy)
  383.     {
  384.     },
  385.  
  386.     onLoad: function(context, spy)
  387.     {
  388.     }
  389. };
  390.  
  391. // ************************************************************************************************
  392.  
  393. function getSpyForXHR(request, xhrRequest, context)
  394. {
  395.     var spy = null;
  396.     var length = context.spies.length;
  397.     for (var i=0; i<length; i++)
  398.     {
  399.         spy = context.spies[i];
  400.         if (spy.request == request)
  401.             return spy;
  402.     }
  403.     
  404.     spy = new XMLHttpRequestSpy(request, xhrRequest, context);
  405.     context.spies.push(spy);
  406.  
  407.     var name = request.URI.asciiSpec;
  408.     var origName = request.originalURI.asciiSpec;
  409.  
  410.     // Attach spy only to the original request. Notice that there
  411.     // can be more network requests made by the same XHR if there
  412.     // are redirects.
  413.     if (name == origName)
  414.         spy.attach();
  415.  
  416.     return spy;
  417. }
  418.  
  419. // ************************************************************************************************
  420.  
  421. function requestStarted(request, xhrRequest, context, method, url)
  422. {
  423.     var spy = getSpyForXHR(request, xhrRequest, context);
  424.     spy.method = method;
  425.     spy.href = url;
  426.  
  427.     if (method == "POST" || method == "PUT")
  428.         spy.postText = readPostTextFromRequest(request, context);
  429.  
  430.     spy.urlParams = parseURLParams(spy.href);
  431.     spy.sourceLink = getStackSourceLink();
  432.  
  433.     if (!spy.requestHeaders)
  434.         spy.requestHeaders = getRequestHeaders(spy);
  435.  
  436.     // If it's enabled log the request into the console tab.
  437.     if (Firebug.showXMLHttpRequests && Firebug.Console.isEnabled(context))
  438.     {
  439.         spy.logRow = Firebug.Console.log(spy, spy.context, "spy", null, true);
  440.         setClass(spy.logRow, "loading");
  441.     }
  442.  
  443.     // Notify registered listeners. The onStart event is fired
  444.     // once for entire XHR (even if there is more redirects within
  445.     // the process).
  446.     var name = request.URI.asciiSpec;
  447.     var origName = request.originalURI.asciiSpec;
  448.     if (name == origName)
  449.         dispatch(listeners, "onStart", [context, spy]);
  450.  
  451.     // Remember the start time et the end, so it's most accurate.
  452.     spy.sendTime = new Date().getTime();
  453. }
  454.  
  455. function requestStopped(request, xhrRequest, context, method, url)
  456. {
  457.     var spy = getSpyForXHR(request, xhrRequest, context);
  458.     if (!spy)
  459.         return;
  460.  
  461.     spy.endTime = new Date().getTime();
  462.     spy.responseTime = spy.endTime - spy.sendTime;
  463.     spy.loaded = true;
  464.  
  465.     if (!spy.responseHeaders)
  466.         spy.responseHeaders = getResponseHeaders(spy);
  467.  
  468.     if (!spy.statusText)
  469.     {
  470.         try
  471.         {
  472.             spy.statusCode = request.responseStatus;
  473.             spy.statusText = request.responseStatusText;
  474.         }
  475.         catch (exc)
  476.         {
  477.         }
  478.     }
  479.  
  480.     if (spy.logRow)
  481.     {
  482.         updateLogRow(spy, spy.responseTime);
  483.         updateHttpSpyInfo(spy);
  484.     }
  485.  
  486.     if (spy.context.spies)
  487.         remove(spy.context.spies, spy);
  488.  
  489. }
  490.  
  491. function onHTTPSpyReadyStateChange(spy, event)
  492. {
  493.     try
  494.     {
  495.         spy.context.onReadySpy = spy; // maybe the handler will eval(), we want the URL.
  496.         if (spy.onreadystatechange)
  497.             spy.onreadystatechange.handleEvent(event);
  498.     }
  499.     catch (exc) 
  500.     { 
  501.     }
  502.     finally
  503.     {
  504.         delete spy.context.onReadySpy;
  505.     }
  506.  
  507.     if (spy.xhrRequest.readyState == 4)
  508.         onHTTPSpyLoad(spy);
  509. }
  510.  
  511. function onHTTPSpyLoad(spy)
  512. {
  513.     // If we were already detached, don't do this again
  514.     if (!spy.onLoad)
  515.         return;
  516.  
  517.     // The main XHR object has to be dettached now (i.e. listeners removed).
  518.     spy.detach();
  519.  
  520.     // The tabCache listener is used to get the actuall response since the
  521.     // spy.xhrRequest.responseText is empty if the request is aborted at this
  522.     // moment. Anyway, this way is used also for following cases:
  523.     // (a) nsITraceableChannel is not available until FF 3.0.4
  524.     // (b) specified response content-type doesn't have to be cached.
  525.     if (!spy.responseText)
  526.         spy.responseText = spy.xhrRequest.responseText;
  527.  
  528.     var netProgress = spy.context.netProgress;
  529.     if (netProgress)
  530.         netProgress.post(netProgress.stopFile, [spy.request, spy.endTime, spy.postText, spy.responseText]);
  531.  
  532.     // If the response is get from FF cache the http-on-examine-response is never sent
  533.     // (https://bugzilla.mozilla.org/show_bug.cgi?id=449198) and so, the requestStopped 
  534.     // method is never called.
  535.     // Let's simulate the event for all spy objects that have been registered for this request.
  536.     // Notice that there can be more spy objects (using the same request object) in case of 
  537.     // redirects.
  538.     var spies = spy.context.spies;
  539.     for (var i=0; spies && i<spies.length; i++)
  540.     {
  541.         if (spy.request == spies[i].request) {
  542.             requestStopped(spy.request, spy.xhrRequest, spy.context, spy.method, spy.href);
  543.             i--;
  544.         }
  545.     }
  546.  
  547.     // Notify registered listeners about finish of the XHR.
  548.     dispatch(listeners, "onLoad", [spy.context, spy]);
  549.  
  550. }
  551.  
  552. function onHTTPSpyError(spy)
  553. {
  554.     var now = new Date().getTime();
  555.  
  556.     if (spy.logRow)
  557.     {
  558.         removeClass(spy.logRow, "loading");
  559.         setClass(spy.logRow, "error");
  560.     }
  561.  
  562.     spy.detach();
  563.  
  564.     if (spy.context.spies)
  565.         remove(spy.context.spies, spy);
  566. }
  567.  
  568. function updateLogRow(spy, responseTime)
  569. {
  570.     var timeBox = getElementByClass(spy.logRow, "spyTime");
  571.     if (responseTime)
  572.         timeBox.textContent = " " + formatTime(responseTime);
  573.  
  574.     var statusBox = getElementByClass(spy.logRow, "spyStatus");
  575.     statusBox.textContent = Firebug.Spy.XHR.getStatus(spy);
  576.  
  577.     removeClass(spy.logRow, "loading");
  578.     setClass(spy.logRow, "loaded");
  579.  
  580.     try
  581.     {
  582.         var errorRange = Math.floor(spy.xhrRequest.status/100);
  583.         if (errorRange == 4 || errorRange == 5)
  584.             setClass(spy.logRow, "error");
  585.     }
  586.     catch (exc) { }
  587. }
  588.  
  589. function updateHttpSpyInfo(spy)
  590. {
  591.     if (!spy.logRow || !hasClass(spy.logRow, "opened"))
  592.         return;
  593.  
  594.     var template = Firebug.NetMonitor.NetInfoBody;
  595.  
  596.     if (!spy.params)
  597.         spy.params = parseURLParams(spy.href+"");
  598.  
  599.     if (!spy.requestHeaders)
  600.         spy.requestHeaders = getRequestHeaders(spy);
  601.  
  602.     if (!spy.responseHeaders && spy.loaded)
  603.         spy.responseHeaders = getResponseHeaders(spy);
  604.  
  605.     var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody");
  606.     if (!netInfoBox)
  607.     {
  608.         var head = getChildByClass(spy.logRow, "spyHead");
  609.         netInfoBox = template.tag.append({"file": spy}, head);
  610.         template.selectTabByName(netInfoBox, "Response");
  611.     }
  612.     else
  613.         template.updateInfo(netInfoBox, spy, spy.context);
  614. }
  615.  
  616. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  617.  
  618. function getRequestHeaders(spy)
  619. {
  620.     var headers = [];
  621.  
  622.     if (spy.xhrRequest.channel instanceof nsIHttpChannel)
  623.     {
  624.         var http = QI(spy.xhrRequest.channel, nsIHttpChannel);
  625.         http.visitRequestHeaders({
  626.             visitHeader: function(name, value)
  627.             {
  628.                 headers.push({name: name, value: value});
  629.             }
  630.         });
  631.     }
  632.  
  633.     return headers;
  634. }
  635.  
  636. function getResponseHeaders(spy)
  637. {
  638.     var headers = [];
  639.  
  640.     try
  641.     {
  642.         if (spy.xhrRequest.channel instanceof nsIHttpChannel)
  643.         {
  644.             var http = QI(spy.xhrRequest.channel, nsIHttpChannel);
  645.             http.visitResponseHeaders({
  646.                 visitHeader: function(name, value)
  647.                 {
  648.                     headers.push({name: name, value: value});
  649.                 }
  650.             });
  651.         }
  652.     }
  653.     catch (exc) { }
  654.  
  655.     return headers;
  656. }
  657.  
  658. // ************************************************************************************************
  659.  
  660. Firebug.registerModule(Firebug.Spy);
  661. Firebug.registerRep(Firebug.Spy.XHR);
  662.  
  663. // ************************************************************************************************
  664.  
  665. }});
  666.