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 / net.js < prev    next >
Encoding:
JavaScript  |  2009-02-19  |  86.7 KB  |  2,895 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 Cr = Components.results;
  11.  
  12. const nsIWebProgressListener = Ci.nsIWebProgressListener;
  13. const nsIWebProgress = Ci.nsIWebProgress;
  14. const nsIRequest = Ci.nsIRequest;
  15. const nsIChannel = Ci.nsIChannel;
  16. const nsIHttpChannel = Ci.nsIHttpChannel;
  17. const nsICacheService = Ci.nsICacheService;
  18. const nsICache = Ci.nsICache;
  19. const nsISupportsWeakReference = Ci.nsISupportsWeakReference;
  20. const nsISupports = Ci.nsISupports;
  21. const nsIIOService = Ci.nsIIOService;
  22. const imgIRequest = Ci.imgIRequest;
  23. const nsIUploadChannel = Ci.nsIUploadChannel;
  24. const nsIXMLHttpRequest = Ci.nsIXMLHttpRequest;
  25. const nsISeekableStream = Ci.nsISeekableStream;
  26. const nsIURI = Ci.nsIURI;
  27.  
  28. const CacheService = Cc["@mozilla.org/network/cache-service;1"];
  29. const ImgCache = Cc["@mozilla.org/image/cache;1"];
  30. const IOService = Cc["@mozilla.org/network/io-service;1"];
  31.  
  32. const nsIPrefBranch2 = Ci.nsIPrefBranch2;
  33. const PrefService = Cc["@mozilla.org/preferences-service;1"];
  34. const prefs = PrefService.getService(nsIPrefBranch2);
  35.  
  36. const NOTIFY_ALL = nsIWebProgress.NOTIFY_ALL;
  37.  
  38. const STATE_IS_WINDOW = nsIWebProgressListener.STATE_IS_WINDOW;
  39. const STATE_IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT;
  40. const STATE_IS_NETWORK = nsIWebProgressListener.STATE_IS_NETWORK;
  41. const STATE_IS_REQUEST = nsIWebProgressListener.STATE_IS_REQUEST;
  42.  
  43. const STATE_START = nsIWebProgressListener.STATE_START;
  44. const STATE_STOP = nsIWebProgressListener.STATE_STOP;
  45. const STATE_TRANSFERRING = nsIWebProgressListener.STATE_TRANSFERRING;
  46.  
  47. const LOAD_BACKGROUND = nsIRequest.LOAD_BACKGROUND;
  48. const LOAD_FROM_CACHE = nsIRequest.LOAD_FROM_CACHE;
  49. const LOAD_DOCUMENT_URI = nsIChannel.LOAD_DOCUMENT_URI;
  50.  
  51. const ACCESS_READ = nsICache.ACCESS_READ;
  52. const STORE_ANYWHERE = nsICache.STORE_ANYWHERE;
  53.  
  54. const NS_ERROR_CACHE_KEY_NOT_FOUND = 0x804B003D;
  55. const NS_ERROR_CACHE_WAIT_FOR_VALIDATION = 0x804B0040;
  56.  
  57. const NS_SEEK_SET = nsISeekableStream.NS_SEEK_SET;
  58.  
  59. const observerService = CCSV("@joehewitt.com/firebug-http-observer;1", "nsIObserverService");
  60.  
  61. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  62.  
  63. const mimeExtensionMap =
  64. {
  65.     "txt": "text/plain",
  66.     "html": "text/html",
  67.     "htm": "text/html",
  68.     "xhtml": "text/html",
  69.     "xml": "text/xml",
  70.     "css": "text/css",
  71.     "js": "application/x-javascript",
  72.     "jss": "application/x-javascript",
  73.     "jpg": "image/jpeg",
  74.     "jpeg": "image/jpeg",
  75.     "gif": "image/gif",
  76.     "png": "image/png",
  77.     "bmp": "image/bmp",
  78.     "swf": "application/x-shockwave-flash",
  79.     "flv": "video/x-flv"
  80. };
  81.  
  82. const fileCategories =
  83. {
  84.     "undefined": 1,
  85.     "html": 1,
  86.     "css": 1,
  87.     "js": 1,
  88.     "xhr": 1,
  89.     "image": 1,
  90.     "flash": 1,
  91.     "txt": 1,
  92.     "bin": 1
  93. };
  94.  
  95. const textFileCategories =
  96. {
  97.     "txt": 1,
  98.     "html": 1,
  99.     "xhr": 1,
  100.     "css": 1,
  101.     "js": 1
  102. };
  103.  
  104. const binaryFileCategories =
  105. {
  106.     "bin": 1,
  107.     "flash": 1
  108. };
  109.  
  110. const mimeCategoryMap =
  111. {
  112.     "text/plain": "txt",
  113.     "application/octet-stream": "bin",
  114.     "text/html": "html",
  115.     "text/xml": "html",
  116.     "text/css": "css",
  117.     "application/x-javascript": "js",
  118.     "text/javascript": "js",
  119.     "application/javascript" : "js",
  120.     "image/jpeg": "image",
  121.     "image/gif": "image",
  122.     "image/png": "image",
  123.     "image/bmp": "image",
  124.     "application/x-shockwave-flash": "flash",
  125.     "video/x-flv": "flash"
  126. };
  127.  
  128. const binaryCategoryMap =
  129. {
  130.     "image": 1,
  131.     "flash" : 1
  132. };
  133.  
  134. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  135.  
  136. const reIgnore = /about:|javascript:|resource:|chrome:|jar:/;
  137. const layoutInterval = 300;
  138. const phaseInterval = 1000;
  139. const indentWidth = 18;
  140. const maxPendingCheck = 200;
  141.  
  142. var cacheSession = null;
  143. var contexts = new Array();
  144. var panelName = "net";
  145. var maxQueueRequests = 500;
  146. var panelBar1 = $("fbPanelBar1");
  147. var listeners = [];
  148.  
  149. // ************************************************************************************************
  150.  
  151. Firebug.NetMonitor = extend(Firebug.ActivableModule,
  152. {
  153.     clear: function(context)
  154.     {
  155.         // The user pressed a Clear button so, remove content of the panel...
  156.         var panel = context.getPanel(panelName, true);
  157.         if (panel)
  158.             panel.clear();
  159.  
  160.         // ... and clear the network context.
  161.         if (context.netProgress)
  162.             context.netProgress.clear();
  163.     },
  164.  
  165.     onToggleFilter: function(context, filterCategory)
  166.     {
  167.         if (!context.netProgress)
  168.             return;
  169.  
  170.         Firebug.setPref(Firebug.prefDomain, "netFilterCategory", filterCategory);
  171.  
  172.         // The content filter has been changed. Make sure that the content
  173.         // of the panel is updated (CSS is used to hide or show individual files).
  174.         var panel = context.getPanel(panelName, true);
  175.         if (panel)
  176.         {
  177.             panel.setFilter(filterCategory);
  178.             panel.updateSummaries(now(), true);
  179.         }
  180.     },
  181.  
  182.     syncFilterButtons: function(chrome)
  183.     {
  184.         var button = chrome.$("fbNetFilter-" + Firebug.netFilterCategory);
  185.         button.checked = true;
  186.     },
  187.  
  188.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  189.     // extends Module
  190.  
  191.     initializeUI: function()
  192.     {
  193.         Firebug.ActivableModule.initializeUI.apply(this, arguments);
  194.  
  195.         // Initialize max limit for logged requests.
  196.         NetLimit.updateMaxLimit();
  197.  
  198.         // Synchronize UI buttons with the current filter.
  199.         this.syncFilterButtons(FirebugChrome);
  200.  
  201.         // Register HTTP observer for all net-request monitoring and time measuring.
  202.         // This is done as soon as the FB UI is loaded.
  203.         HttpObserver.registerObserver();
  204.  
  205.         prefs.addObserver(Firebug.prefDomain, NetLimit, false);
  206.     },
  207.  
  208.     initialize: function()
  209.     {
  210.         this.panelName = panelName;
  211.         this.description = $STR("net.modulemanager.description");
  212.  
  213.         Firebug.ActivableModule.initialize.apply(this, arguments);
  214.     },
  215.  
  216.     shutdown: function()
  217.     {
  218.         // Unregister HTTP observer. This is done when the FB UI is closed.
  219.         HttpObserver.unregisterObserver();
  220.  
  221.         prefs.removeObserver(Firebug.prefDomain, this, false);
  222.     },
  223.  
  224.     initContext: function(context)
  225.     {
  226.         Firebug.ActivableModule.initContext.apply(this, arguments);
  227.  
  228.         var window = context.window;
  229.  
  230.         // Register "load" listener in order to track window load time.
  231.         var onWindowLoadHandler = function() {
  232.             if (context.netProgress)
  233.                 context.netProgress.post(windowLoad, [window, now()]);
  234.             window.removeEventListener("load", onWindowLoadHandler, true);
  235.         }
  236.         window.addEventListener("load", onWindowLoadHandler, true);
  237.  
  238.         // Register "DOMContentLoaded" listener to track timing.
  239.         var onContentLoadHandler = function() {
  240.             if (context.netProgress)
  241.                 context.netProgress.post(contentLoad, [window, now()]);
  242.             window.removeEventListener("DOMContentLoaded", onContentLoadHandler, true);
  243.         }
  244.         window.addEventListener("DOMContentLoaded", onContentLoadHandler, true);
  245.     },
  246.  
  247.     reattachContext: function(browser, context)
  248.     {
  249.         Firebug.ActivableModule.reattachContext.apply(this, arguments);
  250.         var chrome = context ? context.chrome : FirebugChrome;
  251.         this.syncFilterButtons(chrome);
  252.     },
  253.  
  254.     destroyContext: function(context)
  255.     {
  256.         Firebug.ActivableModule.destroyContext.apply(this, arguments);
  257.     },
  258.  
  259.     showContext: function(browser, context)
  260.     {
  261.         Firebug.ActivableModule.showContext.apply(this, arguments);
  262.  
  263.         if (!context)
  264.         {
  265.             var tabId = Firebug.getTabIdForWindow(browser.contentWindow);
  266.             delete contexts[tabId];
  267.         }
  268.     },
  269.  
  270.     loadedContext: function(context)
  271.     {
  272.         if (context.netProgress)
  273.             context.netProgress.loaded = true;
  274.     },
  275.  
  276.     onPanelActivate: function(context, init, activatedPanelName)
  277.     {
  278.         if (activatedPanelName != panelName)
  279.             return;
  280.  
  281.         monitorContext(context);
  282.  
  283.         if (context.netProgress)
  284.         {
  285.             var panel = context.getPanel(panelName);
  286.             context.netProgress.activate(panel);
  287.         }
  288.  
  289.         $('fbStatusIcon').setAttribute("net", "on");
  290.  
  291.         if (!init)
  292.             context.window.location.reload();
  293.     },
  294.  
  295.     onPanelDeactivate: function(context, destroy, deactivatedPanelName)
  296.     {
  297.         if (deactivatedPanelName != panelName)
  298.             return;
  299.  
  300.         if (context.netProgress)
  301.             context.netProgress.activate(null);
  302.  
  303.         unmonitorContext(context);
  304.     },
  305.  
  306.     onLastPanelDeactivate: function(context, destroy)
  307.     {
  308.         $('fbStatusIcon').removeAttribute("net");
  309.     },
  310.  
  311.     onSuspendFirebug: function(context)
  312.     {
  313.         HttpObserver.unregisterObserver();  // safe for multiple calls
  314.         try
  315.         {
  316.             if (context.browser.removeProgressListener)  // XXXjjb often false?
  317.                 context.browser.removeProgressListener(context.netProgress, NOTIFY_ALL);
  318.         }
  319.         catch (e)
  320.         {
  321.         }
  322.  
  323.         $('fbStatusIcon').removeAttribute("net");
  324.     },
  325.  
  326.     onResumeFirebug: function(context)
  327.     {
  328.         HttpObserver.registerObserver();  // safe for multiple calls
  329.         context.browser.addProgressListener(context.netProgress, NOTIFY_ALL);
  330.  
  331.         if (Firebug.NetMonitor.isEnabled(this.context))
  332.             $('fbStatusIcon').setAttribute("net", "on");
  333.     },
  334.  
  335.     addListener: function(listener)
  336.     {
  337.         listeners.push(listener);
  338.     },
  339.  
  340.     removeListener: function(listener)
  341.     {
  342.         remove(listeners, listener);
  343.     }
  344. });
  345.  
  346. // ************************************************************************************************
  347.  
  348. function NetPanel() {}
  349.  
  350. NetPanel.prototype = domplate(Firebug.AblePanel,
  351. {
  352.     tableTag:
  353.         TABLE({class: "netTable", cellpadding: 0, cellspacing: 0, onclick: "$onClick"},
  354.             TBODY(
  355.                 TR(
  356.                     TD({width: "18%"}),
  357.                     TD({width: "12%"}),
  358.                     TD({width: "12%"}),
  359.                     TD({width: "4%"}),
  360.                     TD({width: "54%"})
  361.                 )
  362.             )
  363.         ),
  364.  
  365.     fileTag:
  366.         FOR("file", "$files",
  367.             TR({class: "netRow $file.file|getCategory",
  368.                 $collapsed: "$file.file|hideRow",
  369.                 $hasHeaders: "$file.file|hasResponseHeaders",
  370.                 $loaded: "$file.file.loaded", $responseError: "$file.file|isError",
  371.                 $fromCache: "$file.file.fromCache", $inFrame: "$file.file|getInFrame"},
  372.                 TD({class: "netHrefCol netCol"},
  373.                     DIV({class: "netHrefLabel netLabel",
  374.                          style: "margin-left: $file.file|getIndent\\px"},
  375.                         "$file.file|getHref"
  376.                     ),
  377.                     DIV({class: "netFullHrefLabel netHrefLabel netLabel",
  378.                          style: "margin-left: $file.file|getIndent\\px"},
  379.                         "$file.file.href"
  380.                     )
  381.                 ),
  382.                 TD({class: "netStatusCol netCol"},
  383.                     DIV({class: "netStatusLabel netLabel"}, "$file.file|getStatus")
  384.                 ),
  385.                 TD({class: "netDomainCol netCol"},
  386.                     DIV({class: "netDomainLabel netLabel"}, "$file.file|getDomain")
  387.                 ),
  388.                 TD({class: "netSizeCol netCol"},
  389.                     DIV({class: "netSizeLabel netLabel"}, "$file.file|getSize")
  390.                 ),
  391.                 TD({class: "netTimeCol netCol"},
  392.                     DIV({class: "netBar"},
  393.                         " ",
  394.                         DIV({class: "netConnectingBar", style: "left: $file.offset"}),
  395.                         DIV({class: "netWaitingBar", style: "left: $file.offset"}),
  396.                         DIV({class: "netRespondedBar", style: "left: $file.offset"}),
  397.                         DIV({class: "netContentLoadBar", style: "left: $file.offset"}),
  398.                         DIV({class: "netWindowLoadBar", style: "left: $file.offset"}),
  399.                         DIV({class: "netTimeBar", style: "left: $file.offset; width: $file.width"},
  400.                             SPAN({class: "netTimeLabel"}, "$file.elapsed|formatTime")
  401.                         )
  402.                     )
  403.                 )
  404.             )
  405.         ),
  406.  
  407.     headTag:
  408.         TR({class: "netHeadRow"},
  409.             TD({class: "netHeadCol", colspan: 5},
  410.                 DIV({class: "netHeadLabel"}, "$doc.rootFile.href")
  411.             )
  412.         ),
  413.  
  414.     netInfoTag:
  415.         TR({class: "netInfoRow"},
  416.             TD({class: "netInfoCol", colspan: 5})
  417.         ),
  418.  
  419.     summaryTag:
  420.         TR({class: "netRow netSummaryRow"},
  421.             TD({class: "netCol"},
  422.                 DIV({class: "netCountLabel netSummaryLabel"}, "-")
  423.             ),
  424.             TD({class: "netCol"}),
  425.             TD({class: "netCol"}),
  426.             TD({class: "netTotalSizeCol netCol"},
  427.                 DIV({class: "netTotalSizeLabel netSummaryLabel"}, "0KB")
  428.             ),
  429.             TD({class: "netTotalTimeCol netCol", colspan: 2},
  430.                 DIV({class: "netBar"},
  431.                     DIV({class: "netCacheSizeLabel netSummaryLabel"},
  432.                         "(",
  433.                         SPAN("0KB"),
  434.                         SPAN(" " + $STR("FromCache")),
  435.                         ")"
  436.                     ),
  437.                     DIV({class: "netTimeBar", style: "width: 100%"},
  438.                         SPAN({class: "netTotalTimeLabel netSummaryLabel"}, "0ms")
  439.                     )
  440.                 )
  441.             )
  442.         ),
  443.  
  444.     getCategory: function(file)
  445.     {
  446.         var category = getFileCategory(file);
  447.         if (category)
  448.             return "category-" + category;
  449.  
  450.         return "";
  451.     },
  452.  
  453.     hideRow: function(file)
  454.     {
  455.         return !file.loaded;
  456.     },
  457.  
  458.     getInFrame: function(file)
  459.     {
  460.         return !!file.document.parent;
  461.     },
  462.  
  463.     getIndent: function(file)
  464.     {
  465.         // XXXjoe Turn off indenting for now, it's confusing since we don't
  466.         // actually place nested files directly below their parent
  467.         //return file.document.level * indentWidth;
  468.         return 0;
  469.     },
  470.  
  471.     isError: function(file)
  472.     {
  473.         var errorRange = Math.floor(file.status/100);
  474.         return errorRange == 4 || errorRange == 5;
  475.     },
  476.  
  477.     summarizePhase: function(phase, rightNow)
  478.     {
  479.         var cachedSize = 0, totalSize = 0;
  480.  
  481.         var category = Firebug.netFilterCategory;
  482.         if (category == "all")
  483.             category = null;
  484.  
  485.         var fileCount = 0;
  486.         var minTime = 0, maxTime = 0;
  487.  
  488.         for (var i=0; i<phase.files.length; i++)
  489.         {
  490.             var file = phase.files[i];
  491.  
  492.             if (!category || file.category == category)
  493.             {
  494.                 if (file.loaded)
  495.                 {
  496.                     ++fileCount;
  497.  
  498.                     if (file.size > 0)
  499.                     {
  500.                         totalSize += file.size;
  501.                         if (file.fromCache)
  502.                             cachedSize += file.size;
  503.                     }
  504.  
  505.                     if (!minTime || file.startTime < minTime)
  506.                         minTime = file.startTime;
  507.                     if (file.endTime > maxTime)
  508.                         maxTime = file.endTime;
  509.                 }
  510.             }
  511.         }
  512.  
  513.         var totalTime = maxTime - minTime;
  514.         return {cachedSize: cachedSize, totalSize: totalSize, totalTime: totalTime,
  515.                 fileCount: fileCount}
  516.     },
  517.  
  518.     getHref: function(file)
  519.     {
  520.         return (file.method ? file.method.toUpperCase() : "?") + " " + getFileName(file.href);
  521.     },
  522.  
  523.     getStatus: function(file)
  524.     {
  525.         if (file.responseStatus && file.responseStatusText)
  526.           return file.responseStatus + " " + file.responseStatusText;
  527.  
  528.         return " ";
  529.     },
  530.  
  531.     getDomain: function(file)
  532.     {
  533.         return getPrettyDomain(file.href);
  534.     },
  535.  
  536.     getSize: function(file)
  537.     {
  538.         return this.formatSize(file.size);
  539.     },
  540.  
  541.     hasResponseHeaders: function(file)
  542.     {
  543.         return !!file.responseHeaders;
  544.     },
  545.  
  546.     formatSize: function(bytes)
  547.     {
  548.         if (bytes == -1 || bytes == undefined)
  549.             return "?";
  550.         else if (bytes < 1024)
  551.             return bytes + " B";
  552.         else if (bytes < 1024*1024)
  553.             return Math.ceil(bytes/1024) + " KB";
  554.         else
  555.             return (Math.ceil(bytes/(1024*1024))) + " MB";
  556.     },
  557.  
  558.     formatTime: function(elapsed)
  559.     {
  560.         // Use formatTime util from the lib.
  561.         return formatTime(elapsed);
  562.     },
  563.  
  564.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  565.  
  566.     onClick: function(event)
  567.     {
  568.         if (isLeftClick(event))
  569.         {
  570.             var row = getAncestorByClass(event.target, "netRow");
  571.             if (row)
  572.             {
  573.                 this.toggleHeadersRow(row);
  574.                 cancelEvent(event);
  575.             }
  576.         }
  577.     },
  578.  
  579.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  580.  
  581.     clear: function()
  582.     {
  583.         clearNode(this.panelNode);
  584.  
  585.         this.table = null;
  586.         this.summaryRow = null;
  587.         this.limitRow = null;
  588.  
  589.         this.queue = [];
  590.         this.invalidPhases = false;
  591.     },
  592.  
  593.     setFilter: function(filterCategory)
  594.     {
  595.         this.filterCategory = filterCategory;
  596.  
  597.         var panelNode = this.panelNode;
  598.         for (var category in fileCategories)
  599.         {
  600.             if (filterCategory != "all" && category != filterCategory)
  601.                 setClass(panelNode, "hideCategory-"+category);
  602.             else
  603.                 removeClass(panelNode, "hideCategory-"+category);
  604.         }
  605.     },
  606.  
  607.     toggleHeadersRow: function(row)
  608.     {
  609.         if (!hasClass(row, "hasHeaders"))
  610.             return;
  611.  
  612.         toggleClass(row, "opened");
  613.  
  614.         if (hasClass(row, "opened"))
  615.         {
  616.             var template = Firebug.NetMonitor.NetInfoBody;
  617.  
  618.             var netInfoRow = this.netInfoTag.insertRows({}, row)[0];
  619.             var netInfo = template.tag.replace({file: row.repObject}, netInfoRow.firstChild);
  620.             template.selectTabByName(netInfo, "Headers");
  621.  
  622.             var category = getFileCategory(row.repObject);
  623.             if (category)
  624.                 setClass(netInfo, "category-" + category);
  625.         }
  626.         else
  627.         {
  628.             row.parentNode.removeChild(row.nextSibling);
  629.         }
  630.     },
  631.  
  632.     copyParams: function(file)
  633.     {
  634.         var text = getPostText(file, this.context);
  635.         var url = reEncodeURL(file, text);
  636.         copyToClipboard(url);
  637.     },
  638.  
  639.     copyHeaders: function(headers)
  640.     {
  641.         var lines = [];
  642.         if (headers)
  643.         {
  644.             for (var i = 0; i < headers.length; ++i)
  645.             {
  646.                 var header = headers[i];
  647.                 lines.push(header.name + ": " + header.value);
  648.             }
  649.         }
  650.  
  651.         var text = lines.join("\r\n");
  652.         copyToClipboard(text);
  653.     },
  654.  
  655.     copyResponse: function(file)
  656.     {
  657.         var allowDoublePost = Firebug.getPref(Firebug.prefDomain, "allowDoublePost");
  658.         if (!allowDoublePost && !file.cacheEntry)
  659.         {
  660.             if (!confirm("The response can be re-requested from the server, OK?"))
  661.                 return;
  662.         }
  663.  
  664.         // Copy response to the clipboard
  665.         copyToClipboard(getResponseText(file, this.context));
  666.  
  667.         // Try to update file.cacheEntry flag.
  668.         getCacheEntry(file, this.context.netProgress);
  669.     },
  670.  
  671.     openRequestInTab: function(file)
  672.     {
  673.         var postData = null;
  674.         if (file.postText)
  675.         {
  676.             var stringStream = getInputStreamFromString(file.postText);
  677.             postData = CCIN("@mozilla.org/network/mime-input-stream;1", "nsIMIMEInputStream");
  678.             postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
  679.             postData.addContentLength = true;
  680.             postData.setData(stringStream);
  681.         }
  682.  
  683.         gBrowser.selectedTab = gBrowser.addTab(file.href, null, null, postData);
  684.     },
  685.  
  686.     stopLoading: function(file)
  687.     {
  688.         const NS_BINDING_ABORTED = 0x804b0002;
  689.  
  690.         file.request.cancel(NS_BINDING_ABORTED);
  691.     },
  692.  
  693.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  694.     // extends Panel
  695.  
  696.     name: panelName,
  697.     searchable: true,
  698.     editable: false,
  699.  
  700.     initialize: function(context, doc)
  701.     {
  702.         this.queue = [];
  703.  
  704.         Firebug.Panel.initialize.apply(this, arguments);
  705.     },
  706.  
  707.     destroy: function(state)
  708.     {
  709.         Firebug.Panel.destroy.apply(this, arguments);
  710.     },
  711.  
  712.     show: function(state)
  713.     {
  714.         this.showToolbarButtons("fbNetButtons", true);
  715.  
  716.         var shouldShow = this.shouldShow();
  717.         this.showToolbarButtons("fbNetButtonsFilter", shouldShow);
  718.         if (!shouldShow)
  719.             return;
  720.  
  721.         if (!this.filterCategory)
  722.             this.setFilter(Firebug.netFilterCategory);
  723.  
  724.         this.layout();
  725.         this.layoutInterval = setInterval(bindFixed(this.updateLayout, this), layoutInterval);
  726.  
  727.         if (this.wasScrolledToBottom)
  728.             scrollToBottom(this.panelNode);
  729.     },
  730.  
  731.     shouldShow: function()
  732.     {
  733.         if (Firebug.NetMonitor.isEnabled(this.context))
  734.             return true;
  735.  
  736.         Firebug.ModuleManagerPage.show(this, Firebug.NetMonitor);
  737.  
  738.         return false;
  739.     },
  740.  
  741.     hide: function()
  742.     {
  743.         this.showToolbarButtons("fbNetButtons", false);
  744.         delete this.infoTipURL;  // clear the state that is tracking the infotip so it is reset after next show()
  745.         this.wasScrolledToBottom = isScrolledToBottom(this.panelNode);
  746.  
  747.         clearInterval(this.layoutInterval);
  748.         delete this.layoutInterval;
  749.     },
  750.  
  751.     updateOption: function(name, value)
  752.     {
  753.         if (name == "netFilterCategory")
  754.         {
  755.             Firebug.NetMonitor.syncFilterButtons(this.context.chrome);
  756.             for (var i = 0; i < TabWatcher.contexts.length; ++i)
  757.             {
  758.                 var context = TabWatcher.contexts[i];
  759.                 Firebug.NetMonitor.onToggleFilter(context, value);
  760.             }
  761.         }
  762.     },
  763.  
  764.     getOptionsMenuItems: function()
  765.     {
  766.         return [];
  767.     },
  768.  
  769.     getContextMenuItems: function(nada, target)
  770.     {
  771.         var items = [];
  772.  
  773.         var file = Firebug.getRepObject(target);
  774.         if (!file)
  775.             return items;
  776.  
  777.         var object = Firebug.getObjectByURL(this.context, file.href);
  778.         var isPost = isURLEncodedFile(file, getPostText(file, this.context));
  779.  
  780.         items.push(
  781.             {label: "CopyLocation", command: bindFixed(copyToClipboard, FBL, file.href) }
  782.         );
  783.  
  784.         if (isPost)
  785.         {
  786.             items.push(
  787.                 {label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, file) }
  788.             );
  789.         }
  790.  
  791.         items.push(
  792.             {label: "CopyRequestHeaders",
  793.                 command: bindFixed(this.copyHeaders, this, file.requestHeaders) },
  794.             {label: "CopyResponseHeaders",
  795.                 command: bindFixed(this.copyHeaders, this, file.responseHeaders) }
  796.         );
  797.  
  798.         if ( textFileCategories.hasOwnProperty(file.category) )
  799.         {
  800.             items.push(
  801.                 {label: "CopyResponse", command: bindFixed(this.copyResponse, this, file) }
  802.             );
  803.         }
  804.  
  805.         items.push(
  806.             "-",
  807.             {label: "OpenInTab", command: bindFixed(this.openRequestInTab, this, file) }
  808.         );
  809.  
  810.         if (!file.loaded)
  811.         {
  812.             items.push(
  813.                 "-",
  814.                 {label: "StopLoading", command: bindFixed(this.stopLoading, this, file) }
  815.             );
  816.         }
  817.  
  818.         if (object)
  819.         {
  820.             var subItems = FirebugChrome.getInspectMenuItems(object);
  821.             if (subItems.length)
  822.             {
  823.                 items.push("-");
  824.                 items.push.apply(items, subItems);
  825.             }
  826.         }
  827.  
  828.         return items;
  829.     },
  830.  
  831.     showInfoTip: function(infoTip, target, x, y)
  832.     {
  833.         var row = getAncestorByClass(target, "netRow");
  834.         if (row)
  835.         {
  836.             if (getAncestorByClass(target, "netTimeCol"))
  837.             {
  838.                 var infoTipURL = row.repObject.href + "-nettime";
  839.                 if (infoTipURL == this.infoTipURL)
  840.                     return true;
  841.  
  842.                 this.infoTipURL = infoTipURL;
  843.                 return this.populateTimeInfoTip(infoTip, row.repObject);
  844.             }
  845.             else if (hasClass(row, "category-image"))
  846.             {
  847.                 var infoTipURL = row.repObject.href + "-image";
  848.                 if (infoTipURL == this.infoTipURL)
  849.                     return true;
  850.  
  851.                 this.infoTipURL = infoTipURL;
  852.                 return Firebug.InfoTip.populateImageInfoTip(infoTip, row.repObject.href);
  853.             }
  854.         }
  855.     },
  856.  
  857.     populateTimeInfoTip: function(infoTip, file)
  858.     {
  859.         Firebug.NetMonitor.TimeInfoTip.tag.replace({file: file}, infoTip);
  860.         return true;
  861.     },
  862.  
  863.     search: function(text)
  864.     {
  865.         if (!text)
  866.         {
  867.             delete this.currentSearch;
  868.             return false;
  869.         }
  870.  
  871.         var row;
  872.         if (this.currentSearch && text == this.currentSearch.text)
  873.             row = this.currentSearch.findNext(true);
  874.         else
  875.         {
  876.             function findRow(node) { return getAncestorByClass(node, "netRow"); }
  877.             this.currentSearch = new TextSearch(this.panelNode, findRow);
  878.             row = this.currentSearch.find(text);
  879.         }
  880.  
  881.         if (row)
  882.         {
  883.             var sel = this.document.defaultView.getSelection();
  884.             sel.removeAllRanges();
  885.             sel.addRange(this.currentSearch.range);
  886.  
  887.             scrollIntoCenterView(row, this.panelNode);
  888.             return true;
  889.         }
  890.         else
  891.             return false;
  892.     },
  893.  
  894.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  895.  
  896.     updateFile: function(file)
  897.     {
  898.         if (!file.invalid)
  899.         {
  900.             file.invalid = true;
  901.             this.queue.push(file);
  902.         }
  903.     },
  904.  
  905.     invalidatePhase: function(phase)
  906.     {
  907.         if (phase && !phase.invalidPhase)
  908.         {
  909.             phase.invalidPhase = true;
  910.             this.invalidPhases = true;
  911.         }
  912.     },
  913.  
  914.     updateLayout: function()
  915.     {
  916.         if (!this.queue.length)
  917.             return;
  918.  
  919.         var scrolledToBottom = isScrolledToBottom(this.panelNode);
  920.  
  921.         this.layout();
  922.  
  923.         if (scrolledToBottom)
  924.             scrollToBottom(this.panelNode);
  925.     },
  926.  
  927.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  928.  
  929.     layout: function()
  930.     {
  931.         if (!this.queue.length || !this.context.netProgress ||
  932.             !Firebug.NetMonitor.isEnabled(this.context))
  933.             return;
  934.  
  935.         if (!this.table)
  936.         {
  937.             var limitInfo = {
  938.                 totalCount: 0,
  939.                 limitPrefsTitle: $STRF("LimitPrefsTitle", [Firebug.prefDomain+".net.logLimit"])
  940.             };
  941.  
  942.             this.table = this.tableTag.replace({}, this.panelNode, this);
  943.             this.limitRow = NetLimit.createRow(this.table.firstChild, limitInfo);
  944.             this.summaryRow =  this.summaryTag.insertRows({}, this.table.lastChild.lastChild)[0];
  945.         }
  946.  
  947.         var rightNow = now();
  948.  
  949.         this.updateRowData(rightNow);
  950.         this.updateLogLimit(maxQueueRequests);
  951.         this.updateTimeline(rightNow);
  952.         this.updateSummaries(rightNow);
  953.     },
  954.  
  955.     updateRowData: function(rightNow)
  956.     {
  957.         var queue = this.queue;
  958.         this.queue = [];
  959.  
  960.         var phase;
  961.         var newFileData = [];
  962.  
  963.         for (var i = 0; i < queue.length; ++i)
  964.         {
  965.             var file = queue[i];
  966.             if (!file.phase)
  967.               continue;
  968.  
  969.             file.invalid = false;
  970.  
  971.             phase = this.calculateFileTimes(file, phase, rightNow);
  972.  
  973.             this.updateFileRow(file, newFileData);
  974.             this.invalidatePhase(phase);
  975.         }
  976.  
  977.         if (newFileData.length)
  978.         {
  979.             var tbody = this.table.firstChild;
  980.             var lastRow = tbody.lastChild.previousSibling;
  981.             var row = this.fileTag.insertRows({files: newFileData}, lastRow)[0];
  982.  
  983.             for (var i = 0; i < newFileData.length; ++i)
  984.             {
  985.                 var file = newFileData[i].file;
  986.                 row.repObject = file;
  987.                 file.row = row;
  988.                 row = row.nextSibling;
  989.             }
  990.         }
  991.     },
  992.  
  993.     updateFileRow: function(file, newFileData)
  994.     {
  995.         var row = file.row;
  996.         if (file.toRemove)
  997.         {
  998.             this.removeLogEntry(file, true);
  999.         }
  1000.         else if (!row)
  1001.         {
  1002.             newFileData.push({
  1003.                 file: file,
  1004.                 offset: this.barOffset + "%",
  1005.                 width: this.barWidth + "%",
  1006.                 elapsed: file.loaded ? this.elapsed : -1
  1007.             });
  1008.         }
  1009.         else
  1010.         {
  1011.             var sizeLabel = row.childNodes[3].firstChild;
  1012.             sizeLabel.firstChild.nodeValue = this.getSize(file);
  1013.  
  1014.             var methodLabel = row.childNodes[1].firstChild;
  1015.             methodLabel.firstChild.nodeValue = this.getStatus(file);
  1016.  
  1017.             var hrefLabel = row.childNodes[0].firstChild;
  1018.             hrefLabel.firstChild.nodeValue = this.getHref(file);
  1019.  
  1020.             if (file.mimeType)
  1021.             {
  1022.                 // Force update category.
  1023.                 file.category = null;
  1024.                 removeClass(row, "category-undefined");
  1025.                 setClass(row, "category-"+getFileCategory(file));
  1026.             }
  1027.  
  1028.             if (file.responseHeaders)
  1029.                 setClass(row, "hasHeaders");
  1030.  
  1031.             if (file.fromCache)
  1032.                 setClass(row, "fromCache");
  1033.             else
  1034.                 removeClass(row, "fromCache");
  1035.  
  1036.             if (this.isError(file))
  1037.             {
  1038.                 setClass(row, "responseError");
  1039.  
  1040.                 var hrefLabel = row.firstChild.firstChild.firstChild;
  1041.                 hrefLabel.nodeValue = this.getHref(file);
  1042.             }
  1043.  
  1044.             var timeLabel = row.childNodes[4].firstChild.lastChild.firstChild;
  1045.  
  1046.             if (file.loaded)
  1047.             {
  1048.                 removeClass(row, "collapsed");
  1049.                 setClass(row, "loaded");
  1050.                 timeLabel.innerHTML = this.formatTime(this.elapsed);
  1051.             }
  1052.             else
  1053.             {
  1054.                 removeClass(row, "loaded");
  1055.                 timeLabel.innerHTML = " ";
  1056.             }
  1057.  
  1058.             if (hasClass(row, "opened"))
  1059.             {
  1060.                 var netInfoBox = row.nextSibling.firstChild.firstChild;
  1061.                 Firebug.NetMonitor.NetInfoBody.updateInfo(netInfoBox, file, this.context);
  1062.             }
  1063.         }
  1064.     },
  1065.  
  1066.     updateTimeline: function(rightNow)
  1067.     {
  1068.         //var rootFile = this.context.netProgress.rootFile; // XXXjjb never read?
  1069.         var tbody = this.table.firstChild;
  1070.  
  1071.         // XXXjoe Don't update rows whose phase is done and layed out already
  1072.         var phase;
  1073.         for (var row = tbody.firstChild; row; row = row.nextSibling)
  1074.         {
  1075.             var file = row.repObject;
  1076.  
  1077.             // Some rows aren't associated with a file (e.g. header, sumarry).
  1078.             if (!file)
  1079.                 continue;
  1080.  
  1081.             phase = this.calculateFileTimes(file, phase, rightNow);
  1082.  
  1083.             // Get bar nodes
  1084.             var connectingBar = row.childNodes[4].firstChild.childNodes[1];
  1085.             var waitingBar = connectingBar.nextSibling;
  1086.             var respondedBar = waitingBar.nextSibling;
  1087.             var contentLoadBar = respondedBar.nextSibling;
  1088.             var windowLoadBar = contentLoadBar.nextSibling;
  1089.             var timeBar = windowLoadBar.nextSibling;
  1090.  
  1091.             // All bars starts at the beginning
  1092.             connectingBar.style.left = waitingBar.style.left = respondedBar.style.left = 
  1093.                 timeBar.style.left = this.barOffset + "%";
  1094.  
  1095.             // Sets width of all bars (using style). The width is computed according to measured timing.
  1096.             connectingBar.style.width = this.barConnectingWidth + "%";
  1097.             waitingBar.style.width = this.barWaitingWidth + "%";
  1098.             respondedBar.style.width = this.barRespondedWidth + "%";
  1099.             timeBar.style.width = this.barWidth + "%";
  1100.  
  1101.             if (this.contentLoadBarOffset) {
  1102.                 contentLoadBar.style.left = this.contentLoadBarOffset + "%";
  1103.                 contentLoadBar.style.display = "block";
  1104.                 this.contentLoadBarOffset = null;
  1105.             }
  1106.  
  1107.             if (this.windowLoadBarOffset) {
  1108.                 windowLoadBar.style.left = this.windowLoadBarOffset + "%";
  1109.                 windowLoadBar.style.display = "block";
  1110.                 this.windowLoadBarOffset = null;
  1111.             }
  1112.  
  1113.             /*FBTrace.sysout("net.updateTimeline connecting: " + 
  1114.                 connectingBar.style.left + " : "+  connectingBar.style.width + ", waiting: " + 
  1115.                 waitingBar.style.left + " : " + waitingBar.style.width + ", time: " + 
  1116.                 timeBar.style.left + " : " + timeBar.style.width + ", DOMContentLoaded: " +
  1117.                 contentLoadBar.style.left + ", load: " +
  1118.                 windowLoadBar.style.left, file);*/
  1119.         }
  1120.     },
  1121.  
  1122.     updateSummaries: function(rightNow, updateAll)
  1123.     {
  1124.         if (!this.invalidPhases && !updateAll)
  1125.             return;
  1126.  
  1127.         this.invalidPhases = false;
  1128.  
  1129.         var phases = this.context.netProgress.phases;
  1130.         if (!phases.length)
  1131.             return;
  1132.  
  1133.         var fileCount = 0, totalSize = 0, cachedSize = 0, totalTime = 0;
  1134.         for (var i = 0; i < phases.length; ++i)
  1135.         {
  1136.             var phase = phases[i];
  1137.             phase.invalidPhase = false;
  1138.  
  1139.             var summary = this.summarizePhase(phase, rightNow);
  1140.             fileCount += summary.fileCount;
  1141.             totalSize += summary.totalSize;
  1142.             cachedSize += summary.cachedSize;
  1143.             totalTime += summary.totalTime
  1144.         }
  1145.  
  1146.         var row = this.summaryRow;
  1147.         if (!row)
  1148.             return;
  1149.  
  1150.         var countLabel = row.firstChild.firstChild;
  1151.         countLabel.firstChild.nodeValue = fileCount == 1
  1152.             ? $STR("Request")
  1153.             : $STRF("RequestCount", [fileCount]);
  1154.  
  1155.         var sizeLabel = row.childNodes[3].firstChild;
  1156.         sizeLabel.firstChild.nodeValue = this.formatSize(totalSize);
  1157.  
  1158.         var cacheSizeLabel = row.lastChild.firstChild.firstChild;
  1159.         cacheSizeLabel.setAttribute("collapsed", cachedSize == 0);
  1160.         cacheSizeLabel.childNodes[1].firstChild.nodeValue = this.formatSize(cachedSize);
  1161.  
  1162.         var timeLabel = row.lastChild.firstChild.lastChild.firstChild;
  1163.         timeLabel.innerHTML = this.formatTime(totalTime);
  1164.     },
  1165.  
  1166.     calculateFileTimes: function(file, phase, rightNow)
  1167.     {
  1168.         var phases = this.context.netProgress.phases;
  1169.  
  1170.         if (phase != file.phase)
  1171.         {
  1172.             phase = file.phase;
  1173.             this.phaseStartTime = phase.startTime;
  1174.             this.phaseEndTime = phase.endTime ? phase.endTime : rightNow;
  1175.  
  1176.             // End of the first phase has to respect even the window "onload" event time, which 
  1177.             // can occur after the last received file. This sets the extent of the timeline so, 
  1178.             // the windowLoadBar is visible.
  1179.             if (phase.windowLoadTime && this.phaseEndTime < phase.windowLoadTime)
  1180.                 this.phaseEndTime = phase.windowLoadTime;
  1181.  
  1182.             this.phaseElapsed = this.phaseEndTime - phase.startTime;
  1183.         }
  1184.  
  1185.         var elapsed = file.loaded ? file.endTime - file.startTime : this.phaseEndTime - file.startTime;
  1186.         this.barWidth = Math.floor((elapsed/this.phaseElapsed) * 100);
  1187.         this.barOffset = Math.floor(((file.startTime-this.phaseStartTime)/this.phaseElapsed) * 100);
  1188.         this.barConnectingWidth = 0;//Math.floor(((file.connectingTime - file.startTime)/this.phaseElapsed) * 100); 
  1189.         this.barWaitingWidth = Math.floor(((file.waitingForTime - file.startTime)/this.phaseElapsed) * 100); 
  1190.         this.barRespondedWidth = 0;//Math.floor(((file.respondedTime - file.startTime)/this.phaseElapsed) * 100); 
  1191.  
  1192.         // Total request time doesn't include the time spent in queue.
  1193.         file.elapsed = this.elapsed = elapsed - (file.waitingForTime - file.connectingTime);
  1194.  
  1195.         // Compute also offset for the contentLoadBar and windowLoadBar, which are 
  1196.         // displayed for the first phase.
  1197.         if (phase.contentLoadTime)
  1198.             this.contentLoadBarOffset = Math.floor(((phase.contentLoadTime-this.phaseStartTime)/this.phaseElapsed) * 100);
  1199.  
  1200.         if (phase.windowLoadTime)
  1201.             this.windowLoadBarOffset = Math.floor(((phase.windowLoadTime-this.phaseStartTime)/this.phaseElapsed) * 100);
  1202.  
  1203.         return phase;
  1204.     },
  1205.  
  1206.     updateLogLimit: function(limit)
  1207.     {
  1208.         var netProgress = this.context.netProgress;
  1209.  
  1210.         // Must be positive number;
  1211.         limit = Math.max(0, limit) + netProgress.pending.length;
  1212.  
  1213.         var files = netProgress.files;
  1214.         var filesLength = files.length;
  1215.         if (!filesLength || filesLength <= limit)
  1216.             return;
  1217.  
  1218.         // Remove old requests.
  1219.         var removeCount = Math.max(0, filesLength - limit);
  1220.         for (var i=0; i<removeCount; i++)
  1221.         {
  1222.             var file = files[0];
  1223.             this.removeLogEntry(file);
  1224.  
  1225.             // Remove the file occurrence from the queue.
  1226.             for (var j=0; j<this.queue.length; j++)
  1227.             {
  1228.                 if (this.queue[j] == file) {
  1229.                     this.queue.splice(j, 1);
  1230.                     j--;
  1231.                 }
  1232.             }
  1233.         }
  1234.     },
  1235.  
  1236.     removeLogEntry: function(file, noInfo)
  1237.     {
  1238.         if (!this.removeFile(file))
  1239.             return;
  1240.  
  1241.         if (!this.table || !this.table.firstChild)
  1242.             return;
  1243.  
  1244.         if (file.row)
  1245.         {
  1246.             // The file is loaded and there is a row that has to be removed from the UI.
  1247.             var tbody = this.table.firstChild;
  1248.             tbody.removeChild(file.row);
  1249.         }
  1250.  
  1251.         if (noInfo || !this.limitRow)
  1252.             return;
  1253.  
  1254.         this.limitRow.limitInfo.totalCount++;
  1255.  
  1256.         NetLimit.updateCounter(this.limitRow);
  1257.  
  1258.         //if (netProgress.currentPhase == file.phase)
  1259.         //  netProgress.currentPhase = null;
  1260.     },
  1261.  
  1262.     removeFile: function(file)
  1263.     {
  1264.         var netProgress = this.context.netProgress;
  1265.         var files = netProgress.files;
  1266.         var index = files.indexOf(file);
  1267.         if (index == -1)
  1268.             return false;
  1269.  
  1270.         var requests = netProgress.requests;
  1271.         var phases = netProgress.phases;
  1272.  
  1273.         files.splice(index, 1);
  1274.         requests.splice(index, 1);
  1275.  
  1276.         // Don't forget to remove the phase whose last file has been removed.
  1277.         var phase = file.phase;
  1278.         phase.removeFile(file);
  1279.         if (!phase.files.length)
  1280.         {
  1281.           remove(phases, phase);
  1282.  
  1283.           if (netProgress.currentPhase == phase)
  1284.             netProgress.currentPhase = null;
  1285.         }
  1286.  
  1287.         return true;
  1288.     }
  1289. });
  1290.  
  1291. // ************************************************************************************************
  1292.  
  1293. Firebug.NetMonitor.TimeInfoTip = domplate(Firebug.Rep,
  1294. {
  1295.     tag: 
  1296.         TABLE({class: "timeInfoTip"},
  1297.             TBODY(
  1298.                 /*TR(
  1299.                     TD({class: "netConnectingBar timeInfoTipBar"}),
  1300.                     TD("$file|getConnectintTime : " + $STR("requestinfo.Connecting"))
  1301.                 ),*/
  1302.                 TR(
  1303.                     TD({class: "netWaitingBar timeInfoTipBar"}),
  1304.                     TD("$file|getWaitingTime : " + $STR("requestinfo.Queuing"))
  1305.                 ),
  1306.                 /*TR(
  1307.                     TD({class: "netRespondedBar timeInfoTipBar"}),
  1308.                     TD("$file|getResponseTime : " + $STR("requestinfo.Waiting For Response"))
  1309.                 ),*/
  1310.                 TR({$loaded: "$file.loaded", 
  1311.                     $fromCache: "$file.fromCache"},
  1312.                     TD({class: "netTimeBar timeInfoTipBar"}),
  1313.                     TD("$file|getLoadingTime : " + $STR("requestinfo.Receiving Data"))
  1314.                 ),
  1315.                 TR(
  1316.                     TD({align: "center"},
  1317.                         DIV({class: "netContentLoadBar timeInfoTipBar"})
  1318.                     ),
  1319.                     TD("$file|getContentLoadTime : " + $STR("requestinfo.DOMContentLoaded"))
  1320.                 ),
  1321.                 TR(
  1322.                     TD({align: "center"},
  1323.                         DIV({class: "netWindowLoadBar timeInfoTipBar"})
  1324.                     ),
  1325.                     TD("$file|getWindowLoadTime : " + $STR("requestinfo.Load"))
  1326.                 )
  1327.             )
  1328.         ),
  1329.  
  1330.     getConnectintTime: function(file)
  1331.     {
  1332.         return formatTime(file.connectingTime - file.startTime);
  1333.     },
  1334.  
  1335.     getWaitingTime: function(file)
  1336.     {
  1337.         return formatTime(file.waitingForTime - file.connectingTime);
  1338.     },
  1339.  
  1340.     getResponseTime: function(file)
  1341.     {
  1342.         return formatTime(file.respondedTime - file.waitingForTime);
  1343.     },
  1344.  
  1345.     getLoadingTime: function(file)
  1346.     {
  1347.         return formatTime(file.elapsed);
  1348.     },
  1349.  
  1350.     getWindowLoadTime: function(file)
  1351.     {
  1352.         if (!file.phase.windowLoadTime)
  1353.             return "";
  1354.  
  1355.         var time = file.phase.windowLoadTime - file.startTime;
  1356.         return (time > 0 ? "+" : "") + formatTime(time);
  1357.     },
  1358.  
  1359.     getContentLoadTime: function(file)
  1360.     {
  1361.         if (!file.phase.contentLoadTime)
  1362.             return "";
  1363.  
  1364.         var time = file.phase.contentLoadTime - file.startTime;
  1365.         return (time > 0 ? "+" : "") + formatTime(time);
  1366.     },
  1367. });
  1368.  
  1369. // ************************************************************************************************
  1370.  
  1371. Firebug.NetMonitor.NetLimit = domplate(Firebug.Rep,
  1372. {
  1373.     collapsed: true,
  1374.  
  1375.     tableTag:
  1376.         DIV(
  1377.             TABLE({width: "100%", cellpadding: 0, cellspacing: 0},
  1378.                 TBODY()
  1379.             )
  1380.         ),
  1381.  
  1382.     limitTag:
  1383.         TR({class: "netRow netLimitRow", $collapsed: "$isCollapsed"},
  1384.             TD({class: "netCol netLimitCol", colspan: 5},
  1385.                 TABLE({cellpadding: 0, cellspacing: 0},
  1386.                     TBODY(
  1387.                         TR(
  1388.                             TD(
  1389.                                 SPAN({class: "netLimitLabel"},
  1390.                                     $STR("LimitExceeded")
  1391.                                 )
  1392.                             ),
  1393.                             TD({style: "width:100%"}),
  1394.                             TD(
  1395.                                 BUTTON({class: "netLimitButton", title: "$limitPrefsTitle",
  1396.                                     onclick: "$onPreferences"},
  1397.                                   $STR("LimitPrefs")
  1398.                                 )
  1399.                             ),
  1400.                             TD(" ")
  1401.                         )
  1402.                     )
  1403.                 )
  1404.             )
  1405.         ),
  1406.  
  1407.     isCollapsed: function()
  1408.     {
  1409.         return this.collapsed;
  1410.     },
  1411.  
  1412.     onPreferences: function(event)
  1413.     {
  1414.         openNewTab("about:config");
  1415.     },
  1416.  
  1417.     updateCounter: function(row)
  1418.     {
  1419.         removeClass(row, "collapsed");
  1420.  
  1421.         // Update info within the limit row.
  1422.         var limitLabel = getElementByClass(row, "netLimitLabel");
  1423.         limitLabel.firstChild.nodeValue = $STRF("LimitExceeded", [row.limitInfo.totalCount]);
  1424.     },
  1425.  
  1426.     createTable: function(parent, limitInfo)
  1427.     {
  1428.         var table = this.tableTag.replace({}, parent);
  1429.         var row = this.createRow(table.firstChild.firstChild, limitInfo);
  1430.         return [table, row];
  1431.     },
  1432.  
  1433.     createRow: function(parent, limitInfo)
  1434.     {
  1435.         var row = this.limitTag.insertRows(limitInfo, parent, this)[0];
  1436.         row.limitInfo = limitInfo;
  1437.         return row;
  1438.     },
  1439.  
  1440.     // nsIPrefObserver
  1441.     observe: function(subject, topic, data)
  1442.     {
  1443.         // We're observing preferences only.
  1444.         if (topic != "nsPref:changed")
  1445.           return;
  1446.  
  1447.         if (data.indexOf("net.logLimit") != -1)
  1448.             this.updateMaxLimit();
  1449.     },
  1450.  
  1451.     updateMaxLimit: function()
  1452.     {
  1453.         var value = Firebug.getPref(Firebug.prefDomain, "net.logLimit");
  1454.         maxQueueRequests =  value ? value : maxQueueRequests;
  1455.     }
  1456. });
  1457.  
  1458. var NetLimit = Firebug.NetMonitor.NetLimit;
  1459.  
  1460. // ************************************************************************************************
  1461.  
  1462. function NetProgress(context)
  1463. {
  1464.     this.context = context;
  1465.  
  1466.     var panel = null;
  1467.     var queue = [];
  1468.  
  1469.     this.post = function(handler, args)
  1470.     {
  1471.         if (panel)
  1472.         {
  1473.             var file = handler.apply(this, args);
  1474.             if (file)
  1475.             {
  1476.                 panel.updateFile(file);
  1477.  
  1478.                 // If the panel isn't currently visible, make sure the limit is up to date.
  1479.                 if (!panel.layoutInterval)
  1480.                     panel.updateLogLimit(maxQueueRequests);
  1481.  
  1482.                 return file;
  1483.             }
  1484.         }
  1485.         else
  1486.         {
  1487.             // The first page request is made before the initContext (known problem).
  1488.             queue.push(handler, args);
  1489.         }
  1490.     };
  1491.  
  1492.     this.flush = function()
  1493.     {
  1494.         for (var i=0; i<queue.length; i+=2)
  1495.             this.post(queue[i], queue[i+1]);
  1496.  
  1497.         queue = [];
  1498.     };
  1499.  
  1500.     this.activate = function(activePanel)
  1501.     {
  1502.         this.panel = panel = activePanel;
  1503.         if (panel)
  1504.             this.flush();
  1505.     };
  1506.  
  1507.     this.update = function(file)
  1508.     {
  1509.         if (panel)
  1510.             panel.updateFile(file);
  1511.     };
  1512.  
  1513.     this.clear = function()
  1514.     {
  1515.         this.requests = [];
  1516.         this.files = [];
  1517.         this.phases = [];
  1518.         this.documents = [];
  1519.         this.windows = [];
  1520.  
  1521.         queue = [];
  1522.         requestQueue = [];
  1523.     };
  1524.  
  1525.     // tabCache listener. This must be property of the object itself (not of the prototype).
  1526.     // So the FBL.dispatch method can find it (using hasOwnProperty).
  1527.     this.onStoreResponse = function(win, request, lines)
  1528.     {
  1529.         var file = this.getRequestFile(request, null, true);
  1530.         if (file)
  1531.             file.responseText = lines ? lines.join("\n") : "";
  1532.     };
  1533.  
  1534.     this.clear();
  1535. }
  1536.  
  1537. NetProgress.prototype =
  1538. {
  1539.     panel: null,
  1540.     pending: [],
  1541.  
  1542.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1543.  
  1544.     requestedFile: function requestedFile(request, time, win, xhr)
  1545.     {
  1546.         var file = this.getRequestFile(request, win);
  1547.         if (file)
  1548.         {
  1549.             file.startTime = file.endTime = time;
  1550.             file.waitingForTime = time;
  1551.             file.connectingTime = time;
  1552.             file.respondedTime = time;
  1553.             file.isXHR = xhr;
  1554.             file.isBackground = request.loadFlags & LOAD_BACKGROUND;
  1555.  
  1556.             this.awaitFile(request, file);
  1557.             this.extendPhase(file);
  1558.  
  1559.             return file;
  1560.         }
  1561.         else                                                                                          /*@explore*/
  1562.         {                                                                                             /*@explore*/
  1563.         }                                                                                             /*@explore*/
  1564.     },
  1565.  
  1566.     respondedFile: function respondedFile(request, time, info)
  1567.     {
  1568.         var file = this.getRequestFile(request);
  1569.         if (file)
  1570.         {
  1571.             file.respondedTime = time;
  1572.             file.endTime = time;
  1573.  
  1574.             if (request.contentLength > 0)
  1575.                 file.size = request.contentLength;
  1576.  
  1577.             if (info.responseStatus == 304)
  1578.                 file.fromCache = true;
  1579.             else if (!file.fromCache)
  1580.                 file.fromCache = false;
  1581.  
  1582.             getHttpHeaders(request, file);
  1583.  
  1584.             file.responseStatus = info.responseStatus;
  1585.             file.responseStatusText = info.responseStatusText;
  1586.             file.postText = info.postText;
  1587.  
  1588.             this.endLoad(file);
  1589.             this.arriveFile(file, request);
  1590.  
  1591.             if (file.fromCache)
  1592.                 getCacheEntry(file, this);
  1593.  
  1594.             dispatch(listeners, "onLoad", [this.context, file]);
  1595.  
  1596.             return file;
  1597.         }
  1598.     },
  1599.  
  1600.     waitingForFile: function waitingForFile(request, time)
  1601.     {
  1602.         var file = this.getRequestFile(request, null, true);
  1603.         if (file)
  1604.         {
  1605.             if (!file.receivingStarted) 
  1606.             {
  1607.                 file.waitingForTime = time;
  1608.                 file.receivingStarted = true;
  1609.             }
  1610.         }
  1611.  
  1612.         // Don't update the UI now (optimalization).
  1613.         return null;
  1614.     },
  1615.  
  1616.     connectingFile: function connectingFile(request, time)
  1617.     {
  1618.         var file = this.getRequestFile(request, null, true);
  1619.         if (file)
  1620.         {
  1621.             file.connectingTime = time;
  1622.         }
  1623.  
  1624.         // Don't update the UI now (optimalization).
  1625.         return null;
  1626.     },
  1627.  
  1628.     receivingFile: function receivingFile(request, time)
  1629.     {
  1630.         var file = this.getRequestFile(request, null, true);
  1631.         if (file)
  1632.         {
  1633.             file.endTime = time;
  1634.         }
  1635.  
  1636.         return file;
  1637.     },
  1638.  
  1639.     progressFile: function progressFile(request, progress, expectedSize, time)
  1640.     {
  1641.         var file = this.getRequestFile(request, null, true);
  1642.         if (file)
  1643.         {
  1644.             file.size = progress;
  1645.             file.expectedSize = expectedSize;
  1646.             file.endTime = time;
  1647.  
  1648.             //this.endLoad(file);
  1649.         }
  1650.  
  1651.         return file;
  1652.     },
  1653.  
  1654.     stopFile: function stopFile(request, time, postText, responseText)
  1655.     {
  1656.         var file = this.getRequestFile(request, null, true);
  1657.         if (file)
  1658.         {
  1659.             file.endTime = time;
  1660.             file.postText = postText;
  1661.             file.responseText = responseText;
  1662.  
  1663.             getHttpHeaders(request, file);
  1664.  
  1665.             this.arriveFile(file, request);
  1666.  
  1667.             // Don't mark this file as "loaded". Only request for which the http-on-examine-response
  1668.             // event is received is displayed within the list. This method is used by spy.
  1669.             //this.endLoad(file);
  1670.  
  1671.             getCacheEntry(file, this);
  1672.         }
  1673.  
  1674.         return file;
  1675.     },
  1676.  
  1677.     cacheEntryReady: function cacheEntryReady(request, file, size)
  1678.     {
  1679.         //if (FBTrace.DBG_NET)
  1680.         //    FBTrace.sysout("net.cacheEntryReady for file.href: " + file.href + "\n");
  1681.  
  1682.         if (size != -1)
  1683.             file.size = size;
  1684.  
  1685.         if (file.loaded)
  1686.         {
  1687.             getHttpHeaders(request, file);
  1688.             this.arriveFile(file, request);
  1689.             return file;
  1690.         }
  1691.  
  1692.         // Don't update the UI.
  1693.         return null;
  1694.     },
  1695.  
  1696.     removeFile: function removeFile(request, file, size)
  1697.     {
  1698.         if (file.loaded)
  1699.             return;
  1700.  
  1701.         if (!this.pending.length)
  1702.         {
  1703.             this.context.clearInterval(this.pendingInterval);
  1704.             delete this.pendingInterval;
  1705.         }
  1706.  
  1707.         file.toRemove = true;
  1708.         return file;
  1709.     },
  1710.  
  1711.     windowLoad: function windowLoad(window, time)
  1712.     {
  1713.         if (!this.phases.length)
  1714.             return;
  1715.  
  1716.         // Update all requests that belong to the first phase.
  1717.         var firstPhase = this.phases[0];
  1718.         firstPhase.windowLoadTime = time;
  1719.         for (var i=0; i<firstPhase.files.length; i++)
  1720.             this.panel.updateFile(firstPhase.files[i]);
  1721.  
  1722.         return null;
  1723.     },
  1724.  
  1725.     contentLoad: function contentLoad(window, time)
  1726.     {
  1727.         if (!this.phases.length)
  1728.             return;
  1729.  
  1730.         // Update all requests that belong to the first phase.
  1731.         var firstPhase = this.phases[0];
  1732.         firstPhase.contentLoadTime = time;
  1733.         for (var i=0; i<firstPhase.files.length; i++)
  1734.             this.panel.updateFile(firstPhase.files[i]);
  1735.  
  1736.         return null;
  1737.     },
  1738.  
  1739.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1740.  
  1741.     getRequestFile: function getRequestFile(request, win, noCreate)
  1742.     {
  1743.         var name = safeGetName(request);
  1744.         if (!name || reIgnore.exec(name))
  1745.             return null;
  1746.  
  1747.         var index = this.requests.indexOf(request);
  1748.         if (index == -1 && noCreate)
  1749.             return null;
  1750.  
  1751.         if (index == -1)
  1752.         {
  1753.             if (!win || getRootWindow(win) != this.context.window)
  1754.                 return;
  1755.  
  1756.             var fileDoc = this.getRequestDocument(win);
  1757.             var isDocument = request.loadFlags & LOAD_DOCUMENT_URI && fileDoc.parent;
  1758.             var doc = isDocument ? fileDoc.parent : fileDoc;
  1759.  
  1760.             var file = doc.createFile(request);
  1761.             if (isDocument)
  1762.             {
  1763.                 fileDoc.documentFile = file;
  1764.                 file.ownDocument = fileDoc;
  1765.             }
  1766.  
  1767.             file.request = request;
  1768.             this.requests.push(request);
  1769.             this.files.push(file);
  1770.  
  1771.             return file;
  1772.         }
  1773.  
  1774.         // There is already a file for the reqeust so use it.
  1775.         return this.files[index];
  1776.     },
  1777.  
  1778.     getRequestDocument: function(win)
  1779.     {
  1780.         if (win)
  1781.         {
  1782.             var index = this.windows.indexOf(win);
  1783.             if (index == -1)
  1784.             {
  1785.                 var doc = new NetDocument();
  1786.                 if (win.parent != win)
  1787.                     doc.parent = this.getRequestDocument(win.parent);
  1788.  
  1789.                 //doc.level = getFrameLevel(win);
  1790.  
  1791.                 this.documents.push(doc);
  1792.                 this.windows.push(win);
  1793.  
  1794.                 return doc;
  1795.             }
  1796.             else
  1797.                 return this.documents[index];
  1798.         }
  1799.         else
  1800.             return this.documents[0];
  1801.     },
  1802.  
  1803.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1804.  
  1805.     awaitFile: function(request, file)
  1806.     {
  1807.         //if (FBTrace.DBG_NET)
  1808.         //    FBTrace.sysout("net.awaitFile for file.href: " + file.href + "\n");
  1809.  
  1810.         this.pending.push(file);
  1811.  
  1812.         // XXXjoe Remove files after they have been checked N times
  1813.         if (!this.pendingInterval)
  1814.         {
  1815.             this.pendingInterval = this.context.setInterval(bindFixed(function()
  1816.             {
  1817.                 for (var i = 0; i < this.pending.length; ++i)
  1818.                 {
  1819.                     var file = this.pending[i];
  1820.                     if (file.pendingCount++ > maxPendingCheck)
  1821.                     {
  1822.                         this.pending.splice(i, 1);
  1823.                         --i;
  1824.  
  1825.                         this.post(cacheEntryReady, [request, file, 0]);
  1826.                         this.post(removeFile, [request, file, 0]);
  1827.                     }
  1828.                     else
  1829.                         waitForCacheCompletion(request, file, this);
  1830.                 }
  1831.             }, this), 300);
  1832.         }
  1833.     },
  1834.  
  1835.     arriveFile: function(file, request)
  1836.     {
  1837.         //if (FBTrace.DBG_NET)                                                                                           /*@explore*/
  1838.         //    FBTrace.sysout("net.arriveFile for file.href="+file.href+" and request.name="+safeGetName(request)+"\n");  /*@explore*/
  1839.  
  1840.         var index = this.pending.indexOf(file);
  1841.         if (index != -1)
  1842.             this.pending.splice(index, 1);
  1843.  
  1844.         if (!this.pending.length)
  1845.         {
  1846.             this.context.clearInterval(this.pendingInterval);
  1847.             delete this.pendingInterval;
  1848.         }
  1849.     },
  1850.  
  1851.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1852.  
  1853.     endLoad: function(file)
  1854.     {
  1855.         file.loaded = true;
  1856.  
  1857.         // Update last finished file of the associated phase.
  1858.         file.phase.lastFinishedFile = file;
  1859.     },
  1860.  
  1861.     extendPhase: function(file)
  1862.     {
  1863.         if (this.currentPhase)
  1864.         {
  1865.             // If the new request has been started within a "phaseInterval" after the
  1866.             // previous reqeust has been started, associate it with the current phase;
  1867.             // otherwise create a new phase.
  1868.             var lastStartTime = this.currentPhase.lastStartTime;
  1869.             if (this.loaded && file.startTime - lastStartTime >= phaseInterval)
  1870.                 this.startPhase(file);
  1871.             else
  1872.                 this.currentPhase.addFile(file);
  1873.         }
  1874.         else
  1875.         {
  1876.             // If there is no phase yet, just create it.
  1877.             this.startPhase(file);
  1878.         }
  1879.     },
  1880.  
  1881.     startPhase: function(file)
  1882.     {
  1883.         var phase = new NetPhase(file);
  1884.         phase.initial = !this.currentPhase;
  1885.  
  1886.         this.currentPhase = phase;
  1887.         this.phases.push(phase);
  1888.     },
  1889.  
  1890.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1891.     // nsISupports
  1892.  
  1893.     QueryInterface: function(iid)
  1894.     {
  1895.         if (iid.equals(nsIWebProgressListener)
  1896.             || iid.equals(nsISupportsWeakReference)
  1897.             || iid.equals(nsISupports))
  1898.         {
  1899.             return this;
  1900.         }
  1901.  
  1902.         throw Components.results.NS_NOINTERFACE;
  1903.     },
  1904.  
  1905.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1906.     // nsIWebProgressListener
  1907.  
  1908.     onStateChange: function(progress, request, flag, status)
  1909.     {
  1910.         // For image files we can't get the nsIHttpChannel (the request object is imgIRequest 
  1911.         // in such a case). So, this method is not much useful.
  1912.         var file = this.getRequestFile(request, null, true);
  1913.     },
  1914.  
  1915.     onProgressChange : function(progress, request, current, max, total, maxTotal)
  1916.     {
  1917.         var file = this.getRequestFile(request, null, true);
  1918.         if (file)
  1919.         {
  1920.             this.post(progressFile, [request, current, max, now()]);
  1921.         }
  1922.     },
  1923.  
  1924.     onStatusChange: function(progress, request, status, message) 
  1925.     {
  1926.         var file = this.getRequestFile(request, null, true);
  1927.         if (file)
  1928.         {
  1929.             if (status == Ci.nsISocketTransport.STATUS_CONNECTING_TO || status == Ci.nsISocketTransport.STATUS_CONNECTED_TO)
  1930.                 this.post(connectingFile, [request, now()]);
  1931.             else if (status == Ci.nsISocketTransport.STATUS_WAITING_FOR)
  1932.                 this.post(waitingForFile, [request, now()]);
  1933.             else if (status == Ci.nsISocketTransport.STATUS_RECEIVING_FROM)
  1934.                 this.post(receivingFile, [request, now()]);
  1935.         }
  1936.     },
  1937.  
  1938.     stateIsRequest: false,
  1939.     onLocationChange: function() {},
  1940.     onSecurityChange : function() {},
  1941.     onLinkIconAvailable : function() {},
  1942. };
  1943.  
  1944. var requestedFile = NetProgress.prototype.requestedFile;
  1945. var respondedFile = NetProgress.prototype.respondedFile;
  1946. var connectingFile = NetProgress.prototype.connectingFile;
  1947. var waitingForFile = NetProgress.prototype.waitingForFile;
  1948. var receivingFile = NetProgress.prototype.receivingFile;
  1949. var progressFile = NetProgress.prototype.progressFile;
  1950. var stopFile = NetProgress.prototype.stopFile;
  1951. var cacheEntryReady = NetProgress.prototype.cacheEntryReady;
  1952. var removeFile = NetProgress.prototype.removeFile;
  1953. var windowLoad = NetProgress.prototype.windowLoad;
  1954. var contentLoad = NetProgress.prototype.contentLoad;
  1955.  
  1956. // ************************************************************************************************
  1957.  
  1958. /**
  1959.  * A Document is a helper object that represents a document (window) on the page.
  1960.  * This object is created for main page document and for every embedded document (iframe)
  1961.  * for which a request is made.
  1962.  */
  1963. function NetDocument() { }
  1964.  
  1965. NetDocument.prototype =
  1966. {
  1967.     createFile: function(request)
  1968.     {
  1969.         return new NetFile(request.name, this);
  1970.     }
  1971. };
  1972.  
  1973. // ************************************************************************************************
  1974.  
  1975. /**
  1976.  * A File is a helper object that represents a file for which a request is made.
  1977.  * The document refers to it's parent document (NetDocument) through a member
  1978.  * variable.
  1979.  */
  1980. function NetFile(href, document)
  1981. {
  1982.     this.href = href;
  1983.     this.document = document
  1984.     this.pendingCount = 0;
  1985. }
  1986.  
  1987. NetFile.prototype =
  1988. {
  1989.     status: 0,
  1990.     files: 0,
  1991.     loaded: false,
  1992.     fromCache: false,
  1993.     size: -1,
  1994.     expectedSize: -1,
  1995.     endTime: null,
  1996.     waitingForTime: null,
  1997.     connectingTime: null,
  1998. };
  1999.  
  2000. Firebug.NetFile = NetFile;
  2001.  
  2002. // ************************************************************************************************
  2003.  
  2004. /**
  2005.  * A Phase is a helper object that groups requests made in the same time frame.
  2006.  * In other words, if a new requests is started within a given time (specified
  2007.  * by phaseInterval [ms]) - after previous request has been started -
  2008.  * it automatically belongs to the same phase.
  2009.  * If a request is started after this period, a new phase is created
  2010.  * and this file becomes to be the first in that phase.
  2011.  * The first phase is ended when the page finishes it's loading. Other phases
  2012.  * might be started by additional XHR made by the page.
  2013.  *
  2014.  * All phases are stored within NetProgress.phases array.
  2015.  *
  2016.  * Phases are used to compute size of the graphical timeline. The timeline
  2017.  * for each phase starts from the begining of the graph.
  2018.  */
  2019. function NetPhase(file)
  2020. {
  2021.   // Start time of the phase. Remains the same, even if the file
  2022.   // is removed from the log (due to a max limit of entries).
  2023.   // This ensures stability of the time line.
  2024.   this.startTime = file.startTime;
  2025.  
  2026.   // The last finished request (file) in the phase.
  2027.   this.lastFinishedFile = null;
  2028.  
  2029.   // Set to true if the phase needs to be updated in the UI.
  2030.   this.invalidPhase = null;
  2031.  
  2032.   // List of files associated with this phase.
  2033.   this.files = [];
  2034.  
  2035.   this.addFile(file);
  2036. }
  2037.  
  2038. NetPhase.prototype =
  2039. {
  2040.     addFile: function(file)
  2041.     {
  2042.         this.files.push(file);
  2043.         file.phase = this;
  2044.     },
  2045.  
  2046.     removeFile: function removeFile(file)
  2047.     {
  2048.         remove(this.files, file);
  2049.         file.phase = null;
  2050.  
  2051.         // If the last file has been removed, update the last file member.
  2052.         if (file == this.lastFinishedFile)
  2053.         {
  2054.           if (this.files.length == 0)
  2055.           {
  2056.             this.lastFinishedFile = null;
  2057.           }
  2058.           else
  2059.           {
  2060.             for (var i=0; i<this.files.length; i++) {
  2061.               if (this.lastFinishedFile.endTime < this.files[i].endTime)
  2062.                 this.lastFinishedFile = this.files[i];
  2063.             }
  2064.           }
  2065.         }
  2066.     },
  2067.  
  2068.     get lastStartTime()
  2069.     {
  2070.       return this.files[this.files.length - 1].startTime;
  2071.     },
  2072.  
  2073.     get endTime()
  2074.     {
  2075.       return this.lastFinishedFile ? this.lastFinishedFile.endTime : null;
  2076.     }
  2077. };
  2078.  
  2079. // ************************************************************************************************
  2080. // Local Helpers
  2081.  
  2082. function monitorContext(context)
  2083. {
  2084.     if (!context.netProgress)
  2085.     {
  2086.         var networkContext = null;
  2087.  
  2088.         // Use an existing context associated with the browser tab if any
  2089.         // or create a pure new network context.
  2090.         var tabId = Firebug.getTabIdForWindow(context.window);
  2091.         networkContext = contexts[tabId];
  2092.         if (networkContext) {
  2093.           networkContext.context = context;
  2094.           delete contexts[tabId];
  2095.         }
  2096.         else {
  2097.           networkContext = new NetProgress(context);
  2098.         }
  2099.  
  2100.         var listener = context.netProgress = networkContext;
  2101.  
  2102.         if (context.sourceCache.addListener)
  2103.             context.sourceCache.addListener(networkContext);
  2104.  
  2105.         // This listener is used to observe downlaod progress.
  2106.         context.browser.addProgressListener(listener, NOTIFY_ALL);
  2107.     }
  2108. }
  2109.  
  2110. function unmonitorContext(context)
  2111. {
  2112.     var netProgress = context.netProgress;
  2113.     if (netProgress)
  2114.     {
  2115.         if (netProgress.pendingInterval)
  2116.         {
  2117.             context.clearInterval(netProgress.pendingInterval);
  2118.             delete netProgress.pendingInterval;
  2119.  
  2120.             netProgress.pending.splice(0, netProgress.pending.length);
  2121.         }
  2122.  
  2123.         if (context.sourceCache.removeListener)
  2124.             context.sourceCache.removeListener(netProgress);
  2125.  
  2126.         if (context.browser.docShell)
  2127.             context.browser.removeProgressListener(netProgress, NOTIFY_ALL);
  2128.  
  2129.         delete context.netProgress;
  2130.     }
  2131. }
  2132.  
  2133. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2134.  
  2135. function initCacheSession()
  2136. {
  2137.     if (!cacheSession)
  2138.     {
  2139.         var cacheService = CacheService.getService(nsICacheService);
  2140.         cacheSession = cacheService.createSession("HTTP", STORE_ANYWHERE, true);
  2141.  
  2142.         // xxxHonza setting this to false makes problems with the cache. See Issue 1029.
  2143.         //cacheSession.doomEntriesIfExpired = false;
  2144.     }
  2145. }
  2146.  
  2147. function waitForCacheCompletion(request, file, netProgress)
  2148. {
  2149.     try
  2150.     {
  2151.         initCacheSession();
  2152.         var descriptor = cacheSession.openCacheEntry(file.href, ACCESS_READ, false);
  2153.         if (descriptor)
  2154.             netProgress.post(cacheEntryReady, [request, file, descriptor.dataSize]);
  2155.  
  2156.         //if (FBTrace.DBG_NET)
  2157.         //    FBTrace.sysout("waitForCacheCompletion "+(descriptor?"posted ":"no cache entry ")+file.href+"\n");
  2158.     }
  2159.     catch (exc)
  2160.     {
  2161.         if (exc.result != NS_ERROR_CACHE_WAIT_FOR_VALIDATION
  2162.             && exc.result != NS_ERROR_CACHE_KEY_NOT_FOUND)
  2163.         {
  2164.             ERROR(exc);
  2165.             netProgress.post(cacheEntryReady, [request, file, -1]);
  2166.         }
  2167.     }
  2168. }
  2169.  
  2170. function getCacheEntry(file, netProgress)
  2171. {
  2172.     setTimeout(function delayGetCacheEntry()
  2173.     {
  2174.         try
  2175.         {
  2176.             initCacheSession();
  2177.             cacheSession.asyncOpenCacheEntry(file.href, ACCESS_READ, {
  2178.                 onCacheEntryAvailable: function(descriptor, accessGranted, status)
  2179.                 {
  2180.                     if (descriptor)
  2181.                     {
  2182.                         if(file.size == -1)
  2183.                         {
  2184.                             file.size = descriptor.dataSize;
  2185.                         }
  2186.                         if(descriptor.lastModified && descriptor.lastFetched &&
  2187.                             descriptor.lastModified < Math.floor(file.startTime/1000)) {
  2188.                             file.fromCache = true;
  2189.                         }
  2190.                         file.cacheEntry = [
  2191.                           { name: "Last Modified",
  2192.                             value: getDateFromSeconds(descriptor.lastModified)
  2193.                           },
  2194.                           { name: "Last Fetched",
  2195.                             value: getDateFromSeconds(descriptor.lastFetched)
  2196.                           },
  2197.                           { name: "Expires",
  2198.                             value: getDateFromSeconds(descriptor.expirationTime)
  2199.                           },
  2200.                           { name: "Data Size",
  2201.                             value: descriptor.dataSize
  2202.                           },
  2203.                           { name: "Fetch Count",
  2204.                             value: descriptor.fetchCount
  2205.                           },
  2206.                           { name: "Device",
  2207.                             value: descriptor.deviceID
  2208.                           }
  2209.                         ];
  2210.  
  2211.                         // Get contentType from the cache.
  2212.                         descriptor.visitMetaData({
  2213.                             visitMetaDataElement: function(key, value) {
  2214.                                 if (key == "response-head")
  2215.                                 {
  2216.                                     var contentType = getContentTypeFromResponseHead(value);
  2217.                                     file.mimeType = getMimeType(contentType, file.href);
  2218.                                     return false;
  2219.                                 }
  2220.  
  2221.                                 return true;
  2222.                             }
  2223.                         });
  2224.  
  2225.                         netProgress.update(file);
  2226.                     }
  2227.                 }
  2228.             });
  2229.         }
  2230.         catch (exc)
  2231.         {
  2232.         }
  2233.     });
  2234. }
  2235.  
  2236. function getContentTypeFromResponseHead(value)
  2237. {
  2238.     var values = value.split("\r\n");
  2239.     for (var i=0; i<values.length; i++)
  2240.     {
  2241.         var option = values[i].split(": ");
  2242.         if (option[0] == "Content-Type")
  2243.             return option[1];
  2244.     }
  2245. }
  2246.  
  2247. function getDateFromSeconds(s)
  2248. {
  2249.     var d = new Date();
  2250.     d.setTime(s*1000);
  2251.     return d;
  2252. }
  2253.  
  2254. function getHttpHeaders(request, file)
  2255. {
  2256.     try
  2257.     {
  2258.         var http = QI(request, nsIHttpChannel);
  2259.         file.method = http.requestMethod;
  2260.         file.status = request.responseStatus;
  2261.         file.urlParams = parseURLParams(file.href);
  2262.         file.mimeType = getMimeType(request.contentType, request.name);
  2263.  
  2264.         // Disable temporarily
  2265.         if (!file.responseHeaders && Firebug.collectHttpHeaders)
  2266.         {
  2267.             var requestHeaders = [], responseHeaders = [];
  2268.  
  2269.             http.visitRequestHeaders({
  2270.                 visitHeader: function(name, value)
  2271.                 {
  2272.                     requestHeaders.push({name: name, value: value});
  2273.                 }
  2274.             });
  2275.             http.visitResponseHeaders({
  2276.                 visitHeader: function(name, value)
  2277.                 {
  2278.                     responseHeaders.push({name: name, value: value});
  2279.                 }
  2280.             });
  2281.  
  2282.             file.requestHeaders = requestHeaders;
  2283.             file.responseHeaders = responseHeaders;
  2284.         }
  2285.     }
  2286.     catch (exc)
  2287.     {
  2288.     }
  2289. }
  2290.  
  2291. function isXHR(request)
  2292. {
  2293.     try {
  2294.         if (request.notificationCallbacks)
  2295.             return (request.notificationCallbacks instanceof XMLHttpRequest);
  2296.     }
  2297.     catch (exc) {
  2298.     }
  2299.     return false;
  2300. }
  2301.  
  2302. function safeGetName(request)
  2303. {
  2304.     try
  2305.     {
  2306.         return request.name;
  2307.     }
  2308.     catch (exc) { }
  2309.  
  2310.     return null;
  2311. }
  2312.  
  2313. function getFileCategory(file)
  2314. {
  2315.     if (file.category)
  2316.         return file.category;
  2317.  
  2318.     if (file.isXHR)
  2319.         return "xhr";
  2320.  
  2321.     if (!file.mimeType)
  2322.     {
  2323.         var ext = getFileExtension(file.href);
  2324.         if (ext)
  2325.             file.mimeType = mimeExtensionMap[ext.toLowerCase()];
  2326.     }
  2327.  
  2328.     return (file.category = mimeCategoryMap[file.mimeType]);
  2329. }
  2330.  
  2331. function getMimeType(mimeType, uri)
  2332. {
  2333.     if (!mimeType || !(mimeCategoryMap.hasOwnProperty(mimeType)))
  2334.     {
  2335.         var ext = getFileExtension(uri);
  2336.         if (!ext)
  2337.             return mimeType;
  2338.         else
  2339.         {
  2340.             var extMimeType = mimeExtensionMap[ext.toLowerCase()];
  2341.             return extMimeType ? extMimeType : mimeType;
  2342.         }
  2343.     }
  2344.     else
  2345.         return mimeType;
  2346. }
  2347.  
  2348. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2349.  
  2350. function now()
  2351. {
  2352.     return (new Date()).getTime();
  2353. }
  2354.  
  2355. function getFrameLevel(win)
  2356. {
  2357.     var level = 0;
  2358.  
  2359.     for (; win && (win != win.parent) && (win.parent instanceof Window); win = win.parent)
  2360.         ++level;
  2361.  
  2362.     return level;
  2363. }
  2364.  
  2365. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2366.  
  2367. Firebug.NetMonitor.NetInfoBody = domplate(Firebug.Rep,
  2368. {
  2369.     tag:
  2370.         DIV({class: "netInfoBody", _repObject: "$file"},
  2371.             DIV({class: "netInfoTabs"},
  2372.                 A({class: "netInfoParamsTab netInfoTab", onclick: "$onClickTab",
  2373.                     view: "Params",
  2374.                     $collapsed: "$file|hideParams"},
  2375.                     $STR("URLParameters")
  2376.                 ),
  2377.                 A({class: "netInfoHeadersTab netInfoTab", onclick: "$onClickTab",
  2378.                     view: "Headers"},
  2379.                     $STR("Headers")
  2380.                 ),
  2381.                 A({class: "netInfoPostTab netInfoTab", onclick: "$onClickTab",
  2382.                     view: "Post",
  2383.                     $collapsed: "$file|hidePost"},
  2384.                     $STR("Post")
  2385.                 ),
  2386.                 A({class: "netInfoPutTab netInfoTab", onclick: "$onClickTab",
  2387.                     view: "Put",
  2388.                     $collapsed: "$file|hidePut"},
  2389.                     $STR("Put")
  2390.                 ),
  2391.                 A({class: "netInfoResponseTab netInfoTab", onclick: "$onClickTab",
  2392.                     view: "Response",
  2393.                     $collapsed: "$file|hideResponse"},
  2394.                     $STR("Response")
  2395.                 ),
  2396.                 A({class: "netInfoCacheTab netInfoTab", onclick: "$onClickTab",
  2397.                    view: "Cache",
  2398.                    $collapsed: "$file|hideCache"},
  2399.                    $STR("Cache")
  2400.                 )
  2401.             ),
  2402.             TABLE({class: "netInfoParamsText netInfoText netInfoParamsTable",
  2403.                     cellpadding: 0, cellspacing: 0}, TBODY()),
  2404.             TABLE({class: "netInfoHeadersText netInfoText netInfoHeadersTable",
  2405.                     cellpadding: 0, cellspacing: 0},
  2406.                 TBODY(
  2407.                     TR({class: "netInfoResponseHeadersTitle"},
  2408.                         TD({colspan: 2},
  2409.                             DIV({class: "netInfoHeadersGroup"}, $STR("ResponseHeaders"))
  2410.                         )
  2411.                     ),
  2412.                     TR({class: "netInfoRequestHeadersTitle"},
  2413.                         TD({colspan: 2},
  2414.                             DIV({class: "netInfoHeadersGroup"}, $STR("RequestHeaders"))
  2415.                         )
  2416.                     )
  2417.                 )
  2418.             ),
  2419.             DIV({class: "netInfoPostText netInfoText"},
  2420.                 TABLE({class: "netInfoPostTable", cellpadding: 0, cellspacing: 0},
  2421.                     TBODY()
  2422.                 )
  2423.             ),
  2424.             DIV({class: "netInfoPutText netInfoText"},
  2425.                 TABLE({class: "netInfoPutTable", cellpadding: 0, cellspacing: 0},
  2426.                     TBODY()
  2427.                 )
  2428.             ),
  2429.             DIV({class: "netInfoResponseText netInfoText"},
  2430.                 DIV({class: "loadResponseMessage"}),
  2431.                 BUTTON({onclick: "$onLoadResponse"},
  2432.                     SPAN("Load Response")
  2433.                 )
  2434.             ),
  2435.             DIV({class: "netInfoCacheText netInfoText"},
  2436.                 TABLE({class: "netInfoCacheTable", cellpadding: 0, cellspacing: 0},
  2437.                     TBODY()
  2438.                 )
  2439.             )
  2440.         ),
  2441.  
  2442.     headerDataTag:
  2443.         FOR("param", "$headers",
  2444.             TR(
  2445.                 TD({class: "netInfoParamName"}, "$param.name"),
  2446.                 TD({class: "netInfoParamValue"}, 
  2447.                     PRE("$param|getParamValue")
  2448.                 )
  2449.             )
  2450.         ),
  2451.  
  2452.     hideParams: function(file)
  2453.     {
  2454.         return !file.urlParams || !file.urlParams.length;
  2455.     },
  2456.  
  2457.     hidePost: function(file)
  2458.     {
  2459.         return file.method.toUpperCase() != "POST";
  2460.     },
  2461.  
  2462.     hidePut: function(file)
  2463.     {
  2464.         return file.method.toUpperCase() != "PUT";
  2465.     },
  2466.  
  2467.     hideResponse: function(file)
  2468.     {
  2469.         return file.category in binaryFileCategories;
  2470.     },
  2471.  
  2472.     hideCache: function(file)
  2473.     {
  2474.         return !file.cacheEntry || file.category=="image";
  2475.     },
  2476.  
  2477.     onClickTab: function(event)
  2478.     {
  2479.         this.selectTab(event.currentTarget);
  2480.     },
  2481.  
  2482.     getParamValue: function(param)
  2483.     {
  2484.         // This value is inserted into PRE element and so, make sure the HTML isn't escaped (1210).
  2485.         // This is why the second parameter is true. 
  2486.         // The PRE element preserves whitespaces so they are displayed the same, as they come from 
  2487.         // the server (1194).
  2488.         return wrapText(param.value, true);
  2489.     },
  2490.  
  2491.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2492.  
  2493.     selectTabByName: function(netInfoBox, tabName)
  2494.     {
  2495.         var tab = getChildByClass(netInfoBox, "netInfoTabs", "netInfo"+tabName+"Tab");
  2496.         if (tab)
  2497.             this.selectTab(tab);
  2498.     },
  2499.  
  2500.     selectTab: function(tab)
  2501.     {
  2502.         var netInfoBox = tab.parentNode.parentNode;
  2503.  
  2504.         var view = tab.getAttribute("view");
  2505.         if (netInfoBox.selectedTab)
  2506.         {
  2507.             netInfoBox.selectedTab.removeAttribute("selected");
  2508.             netInfoBox.selectedText.removeAttribute("selected");
  2509.         }
  2510.  
  2511.         var textBodyName = "netInfo" + view + "Text";
  2512.  
  2513.         netInfoBox.selectedTab = tab;
  2514.         netInfoBox.selectedText = getChildByClass(netInfoBox, textBodyName);
  2515.  
  2516.         netInfoBox.selectedTab.setAttribute("selected", "true");
  2517.         netInfoBox.selectedText.setAttribute("selected", "true");
  2518.  
  2519.         var file = Firebug.getRepObject(netInfoBox);
  2520.         var context = Firebug.getElementPanel(netInfoBox).context;
  2521.         this.updateInfo(netInfoBox, file, context);
  2522.     },
  2523.  
  2524.     updateInfo: function(netInfoBox, file, context)
  2525.     {
  2526.         var tab = netInfoBox.selectedTab;
  2527.         if (hasClass(tab, "netInfoParamsTab"))
  2528.         {
  2529.             if (file.urlParams && !netInfoBox.urlParamsPresented)
  2530.             {
  2531.                 netInfoBox.urlParamsPresented = true;
  2532.                 this.insertHeaderRows(netInfoBox, file.urlParams, "Params");
  2533.             }
  2534.         }
  2535.  
  2536.         if (hasClass(tab, "netInfoHeadersTab"))
  2537.         {
  2538.             if (file.responseHeaders && !netInfoBox.responseHeadersPresented)
  2539.             {
  2540.                 netInfoBox.responseHeadersPresented = true;
  2541.                 this.insertHeaderRows(netInfoBox, file.responseHeaders, "Headers", "ResponseHeaders");
  2542.             }
  2543.  
  2544.             if (file.requestHeaders && !netInfoBox.requestHeadersPresented)
  2545.             {
  2546.                 netInfoBox.requestHeadersPresented = true;
  2547.                 this.insertHeaderRows(netInfoBox, file.requestHeaders, "Headers", "RequestHeaders");
  2548.             }
  2549.         }
  2550.  
  2551.         if (hasClass(tab, "netInfoPostTab"))
  2552.         {
  2553.             var postTextBox = getElementByClass(netInfoBox, "netInfoPostText");
  2554.             if (!netInfoBox.postPresented)
  2555.             {
  2556.                 netInfoBox.postPresented  = true;
  2557.  
  2558.                 var text = getPostText(file, context);
  2559.                 if (text != undefined)
  2560.                 {
  2561.                     if (isURLEncodedFile(file, text))
  2562.                     {
  2563.                         var lines = text.split("\n");
  2564.                         var params = parseURLEncodedText(lines[lines.length-1]);
  2565.                         this.insertHeaderRows(netInfoBox, params, "Post");
  2566.                     }
  2567.                     else
  2568.                     {
  2569.                         var postText = formatPostText(text);
  2570.                         if (postText)
  2571.                             insertWrappedText(postText, postTextBox);
  2572.                     }
  2573.                 }
  2574.             }
  2575.         }
  2576.  
  2577.         if (hasClass(tab, "netInfoPutTab"))
  2578.         {
  2579.             var putTextBox = getElementByClass(netInfoBox, "netInfoPutText");
  2580.             if (!netInfoBox.putPresented)
  2581.             {
  2582.                 netInfoBox.putPresented  = true;
  2583.  
  2584.                 var text = getPostText(file, context);
  2585.                 if (text != undefined)
  2586.                 {
  2587.                     if (isURLEncodedFile(file, text))
  2588.                     {
  2589.                         var lines = text.split("\n");
  2590.                         var params = parseURLEncodedText(lines[lines.length-1]);
  2591.                         this.insertHeaderRows(netInfoBox, params, "Put");
  2592.                     }
  2593.                     else
  2594.                     {
  2595.                         var putText = formatPostText(text);
  2596.                         if (putText)
  2597.                             insertWrappedText(putText, putTextBox);
  2598.                     }
  2599.                 }
  2600.             }
  2601.         }
  2602.  
  2603.         if (hasClass(tab, "netInfoResponseTab") && file.loaded && !netInfoBox.responsePresented)
  2604.         {
  2605.             var responseTextBox = getElementByClass(netInfoBox, "netInfoResponseText");
  2606.             if (file.category == "image")
  2607.             {
  2608.                 netInfoBox.responsePresented = true;
  2609.  
  2610.                 var responseImage = netInfoBox.ownerDocument.createElement("img");
  2611.                 responseImage.src = file.href;
  2612.  
  2613.                 clearNode(responseTextBox);
  2614.                 responseTextBox.appendChild(responseImage, responseTextBox);
  2615.             }
  2616.             else if (!(binaryCategoryMap.hasOwnProperty(file.category)))
  2617.             {
  2618.                 var allowDoublePost = Firebug.getPref(Firebug.prefDomain, "allowDoublePost");
  2619.  
  2620.                 // If the response is in the cache get it and display it;
  2621.                 // otherwise display a button, which can be used by the user
  2622.                 // to re-request the response from the server.
  2623.  
  2624.                 // xxxHonza this is a workaround, which should be removed
  2625.                 // as soon as the #430155 is fixed.
  2626.                 // xxxHonza: OK, #430155 is fixed this must be removed.
  2627.                 if (Ci.nsITraceableChannel || allowDoublePost || file.cacheEntry)
  2628.                 {
  2629.                     this.setResponseText(file, netInfoBox, responseTextBox, context);
  2630.                 }
  2631.                 else
  2632.                 {
  2633.                     var msgBox = getElementByClass(netInfoBox, "loadResponseMessage");
  2634.                     msgBox.innerHTML = doublePostForbiddenMessage(file.href);
  2635.                 }
  2636.             }
  2637.         }
  2638.  
  2639.         if (hasClass(tab, "netInfoCacheTab") && file.loaded && !netInfoBox.cachePresented)
  2640.         {
  2641.             netInfoBox.cachePresented = true;
  2642.  
  2643.             var responseTextBox = getElementByClass(netInfoBox, "netInfoCacheText");
  2644.             if (file.cacheEntry) {
  2645.                 this.insertHeaderRows(netInfoBox, file.cacheEntry, "Cache");
  2646.             }
  2647.         }
  2648.     },
  2649.  
  2650.     setResponseText: function(file, netInfoBox, responseTextBox, context)
  2651.     {
  2652.         var text = getResponseText(file, context);
  2653.         if (text)
  2654.             insertWrappedText(text, responseTextBox);
  2655.         else
  2656.             insertWrappedText("", responseTextBox);
  2657.  
  2658.         netInfoBox.responsePresented = true;
  2659.  
  2660.         // Try to get the data from cache and update file.cacheEntry so,
  2661.         // the response is displayed automatically the next time the
  2662.         // net-entry is expanded again.
  2663.         getCacheEntry(file, context.netProgress);
  2664.     },
  2665.  
  2666.     onLoadResponse: function(event)
  2667.     {
  2668.         var file = Firebug.getRepObject(event.target);
  2669.         var netInfoBox = getAncestorByClass(event.target, "netInfoBody");
  2670.         var responseTextBox = getElementByClass(netInfoBox, "netInfoResponseText");
  2671.  
  2672.         this.setResponseText(file, netInfoBox, responseTextBox, FirebugContext);
  2673.     },
  2674.  
  2675.     insertHeaderRows: function(netInfoBox, headers, tableName, rowName)
  2676.     {
  2677.         var headersTable = getElementByClass(netInfoBox, "netInfo"+tableName+"Table");
  2678.         var tbody = headersTable.firstChild;
  2679.         var titleRow = getChildByClass(tbody, "netInfo" + rowName + "Title");
  2680.  
  2681.         if (headers.length)
  2682.         {
  2683.             this.headerDataTag.insertRows({headers: headers}, titleRow ? titleRow : tbody);
  2684.             removeClass(titleRow, "collapsed");
  2685.         }
  2686.         else
  2687.             setClass(titleRow, "collapsed");
  2688.     }
  2689. });
  2690.  
  2691. function doublePostForbiddenMessage(url)
  2692. {
  2693.     var msg = "Firebug needs to POST to the server to get this information for url:<br/><b>" + url + "</b><br/><br/>";
  2694.     msg += "This second POST can interfere with some sites.";
  2695.     msg += " If you want to send the POST again, open a new tab in Firefox, use URL 'about:config', ";
  2696.     msg += "set boolean value 'extensions.firebug.allowDoublePost' to true<br/>";
  2697.     msg += " This value is reset every time you restart Firefox";
  2698.     msg += " This problem will disappear when https://bugzilla.mozilla.org/show_bug.cgi?id=430155 is shipped.<br/><br/>";
  2699.  
  2700.     return msg;
  2701. }
  2702.  
  2703. // ************************************************************************************************
  2704.  
  2705. function findHeader(headers, name)
  2706. {
  2707.     for (var i = 0; i < headers.length; ++i)
  2708.     {
  2709.         if (headers[i].name == name)
  2710.             return headers[i].value;
  2711.     }
  2712. }
  2713.  
  2714. function formatPostText(text)
  2715. {
  2716.     if (text instanceof XMLDocument)
  2717.         return getElementXML(text.documentElement);
  2718.     else
  2719.         return text;
  2720. }
  2721.  
  2722. function getPostText(file, context)
  2723. {
  2724.     if (!file.postText)
  2725.         file.postText = readPostTextFromPage(file.href, context);
  2726.  
  2727.     if (!file.postText)
  2728.         file.postText = readPostTextFromRequest(file.request, context);
  2729.  
  2730.     return file.postText;
  2731. }
  2732.  
  2733. function getResponseText(file, context)
  2734. {
  2735.     // The response can be also empty string so, check against "undefined".
  2736.     return (typeof(file.responseText) != "undefined")? file.responseText :
  2737.         context.sourceCache.loadText(file.href, file.method, file);
  2738. }
  2739.  
  2740. function isURLEncodedFile(file, text)
  2741. {
  2742.     if (text && text.indexOf("Content-Type: application/x-www-form-urlencoded") != -1)
  2743.         return true;
  2744.  
  2745.     // The header value doesn't have to be alway exactly "application/x-www-form-urlencoded",
  2746.     // there can be even charset specified. So, use indexOf rather than just "==".
  2747.     var headerValue = findHeader(file.requestHeaders, "Content-Type");
  2748.     if (headerValue && headerValue.indexOf("application/x-www-form-urlencoded") == 0)
  2749.         return true;
  2750.  
  2751.     return false;
  2752. }
  2753.  
  2754. // ************************************************************************************************
  2755.  
  2756. // HTTP listener - based on firebug-http-observer component
  2757. // This observer is used for observing the first document http-on-modify-request
  2758. // and http-on-examine-response events, which are fired before the context
  2759. // is initialized (initContext method call). Without this observer this events
  2760. // would be lost and the time measuring would be wrong.
  2761. //
  2762. // This observer stores these early requests in helper array (contexts) and maps
  2763. // them to appropriate tab - initContext then uses the array in order to access it.
  2764. //-----------------------------------------------------------------------------
  2765.  
  2766. var HttpObserver =
  2767. {
  2768.     registered: false,
  2769.  
  2770.     registerObserver: function()
  2771.     {
  2772.         if (this.registered)
  2773.             return;
  2774.  
  2775.         observerService.addObserver(this, "firebug-http-event", false);
  2776.         this.registered = true;
  2777.     },
  2778.  
  2779.     unregisterObserver: function()
  2780.     {
  2781.         if (!this.registered)
  2782.             return;
  2783.  
  2784.         observerService.removeObserver(this, "firebug-http-event");
  2785.         this.registered = false;
  2786.     },
  2787.  
  2788.     /* nsIObserve */
  2789.     observe: function(subject, topic, data)
  2790.     {
  2791.         try 
  2792.         {
  2793.             if (!(subject instanceof Ci.nsIHttpChannel))
  2794.                 return;
  2795.  
  2796.             var win = getWindowForRequest(subject);
  2797.             var context = TabWatcher.getContextByWindow(win);
  2798.             if (!Firebug.NetMonitor.isEnabled(context))
  2799.                 return;
  2800.  
  2801.             // Some requests are not associted with any page (e.g. favicon).
  2802.             // These are ignored as Net panel shows only page requests.
  2803.             var tabId = Firebug.getTabIdForWindow(win);
  2804.             if (!(tabId && win))
  2805.                 return;
  2806.  
  2807.             if (topic == "http-on-modify-request")
  2808.                 this.onModifyRequest(subject, win, tabId, context);
  2809.             else if (topic == "http-on-examine-response")
  2810.                 this.onExamineResponse(subject, win, tabId, context);
  2811.         }
  2812.         catch (err)
  2813.         {
  2814.         }
  2815.     },
  2816.  
  2817.     onModifyRequest: function(request, win, tabId, context)
  2818.     {
  2819.         var name = request.URI.asciiSpec;
  2820.         var origName = request.originalURI.asciiSpec;
  2821.         var isRedirect = (name != origName);
  2822.  
  2823.         // We only need to create a new context if this is a top document uri (not frames).
  2824.         if ((request.loadFlags & nsIChannel.LOAD_DOCUMENT_URI) &&
  2825.             request.loadGroup && request.loadGroup.groupObserver &&
  2826.             win == win.parent && !isRedirect)
  2827.         {
  2828.             // Create a new network context prematurely.
  2829.             if (!contexts[tabId])
  2830.             contexts[tabId] = new NetProgress(null);
  2831.         }
  2832.  
  2833.         var networkContext = contexts[tabId];
  2834.         if (!networkContext)
  2835.             networkContext = context ? context.netProgress : null;
  2836.  
  2837.         if (networkContext) 
  2838.         {
  2839.             var xhr = isXHR(request);
  2840.             networkContext.post(requestedFile, [request, now(), win, xhr]);
  2841.         }
  2842.     },
  2843.  
  2844.     onExamineResponse: function(request, win, tabId, context)
  2845.     {
  2846.         var networkContext = contexts[tabId];
  2847.         if (!networkContext)
  2848.             networkContext = context ? context.netProgress : null;
  2849.  
  2850.         var info = new Object();
  2851.         info.responseStatus = request.responseStatus;
  2852.         info.responseStatusText = request.responseStatusText;
  2853.         info.postText = readPostTextFromRequest(request, context);
  2854.  
  2855.         if (!info.postText)
  2856.             info.postText = readPostTextFromPage(request.name, context);
  2857.  
  2858.         if (networkContext)
  2859.             networkContext.post(respondedFile, [request, now(), info]);
  2860.     },
  2861.  
  2862.     /* nsISupports */
  2863.     QueryInterface: function(iid) 
  2864.     {
  2865.         if (iid.equals(Ci.nsISupports) || 
  2866.             iid.equals(Ci.nsIObserver)) {
  2867.              return this;
  2868.          }
  2869.         
  2870.         throw Cr.NS_ERROR_NO_INTERFACE;
  2871.     }
  2872. }
  2873.  
  2874. // ************************************************************************************************
  2875. // Helper for tracing 
  2876.  
  2877. function getPrintableTime()
  2878. {
  2879.     var date = new Date();
  2880.     return "(" + date.getSeconds() + ":" + date.getMilliseconds() + ")";
  2881. }
  2882.  
  2883. // ************************************************************************************************
  2884.  
  2885. Firebug.registerActivableModule(Firebug.NetMonitor);
  2886. Firebug.registerPanel(NetPanel);
  2887.  
  2888. // ************************************************************************************************
  2889.  
  2890. }});
  2891.  
  2892.  
  2893.  
  2894.  
  2895.