1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/chrome/firefox",
  7     "firebug/lib/domplate",
  8     "firebug/lib/locale",
  9     "firebug/lib/events",
 10     "firebug/lib/options",
 11     "firebug/lib/url",
 12     "firebug/lib/css",
 13     "firebug/lib/dom",
 14     "firebug/chrome/window",
 15     "firebug/lib/search",
 16     "firebug/lib/string",
 17     "firebug/lib/json",
 18     "firebug/lib/array",
 19     "firebug/dom/toggleBranch",
 20     "firebug/lib/dragdrop",
 21     "firebug/net/netUtils",
 22     "firebug/net/netProgress",
 23     "firebug/lib/http",
 24     "firebug/js/breakpoint",
 25     "firebug/net/xmlViewer",
 26     "firebug/net/svgViewer",
 27     "firebug/net/jsonViewer",
 28     "firebug/net/fontViewer",
 29     "firebug/chrome/infotip",
 30     "firebug/css/cssPanel",
 31     "firebug/chrome/searchBox",
 32     "firebug/console/errors",
 33     "firebug/net/netMonitor"
 34 ],
 35 function(Obj, Firebug, Firefox, Domplate, Locale, Events, Options, Url, Css, Dom, Win, Search, Str,
 36     Json, Arr, ToggleBranch, DragDrop, NetUtils, NetProgress, Http) {
 37 
 38 with (Domplate) {
 39 
 40 // ********************************************************************************************* //
 41 // Constants
 42 
 43 const Cc = Components.classes;
 44 const Ci = Components.interfaces;
 45 const Cr = Components.results;
 46 
 47 const hiddenColsPref = "net.hiddenColumns";
 48 
 49 var panelName = "net";
 50 
 51 // ********************************************************************************************* //
 52 
 53 const reSplitIP = /^(\d+)\.(\d+)\.(\d+)\.(\d+):(\d+)$/;
 54 
 55 /**
 56  * @domplate Represents a template that is used to render basic content of the net panel.
 57  */
 58 Firebug.NetMonitor.NetRequestTable = domplate(Firebug.Rep, new Firebug.Listener(),
 59 {
 60     inspectable: false,
 61 
 62     tableTag:
 63         TABLE({"class": "netTable", cellpadding: 0, cellspacing: 0, hiddenCols: "",
 64             "role": "treegrid"},
 65             THEAD(
 66                 TR({"class": "netHeaderRow netRow focusRow outerFocusRow",
 67                     onclick: "$onClickHeader", "role": "row"},
 68                     TD({id: "netBreakpointBar", width: "1%", "class": "netHeaderCell",
 69                         "role": "columnheader"},
 70                         " "
 71                     ),
 72                     TD({id: "netHrefCol", width: "18%", "class": "netHeaderCell alphaValue a11yFocus",
 73                         "role": "columnheader"},
 74                         DIV({"class": "netHeaderCellBox",
 75                             title: Locale.$STR("net.header.URL Tooltip")},
 76                             Locale.$STR("net.header.URL")
 77                         )
 78                     ),
 79                     TD({id: "netStatusCol", width: "12%", "class": "netHeaderCell alphaValue a11yFocus",
 80                         "role": "columnheader"},
 81                         DIV({"class": "netHeaderCellBox",
 82                             title: Locale.$STR("net.header.Status Tooltip")},
 83                             Locale.$STR("net.header.Status")
 84                         )
 85                     ),
 86                     TD({id: "netProtocolCol", width: "4%", "class": "netHeaderCell alphaValue a11yFocus",
 87                         "role": "columnheader"},
 88                         DIV({"class": "netHeaderCellBox",
 89                             title: Locale.$STR("net.header.Protocol Tooltip")},
 90                             Locale.$STR("net.header.Protocol")
 91                         )
 92                     ),
 93                     TD({id: "netDomainCol", width: "12%", "class": "netHeaderCell alphaValue a11yFocus",
 94                         "role": "columnheader"},
 95                         DIV({"class": "netHeaderCellBox",
 96                             title: Locale.$STR("net.header.Domain Tooltip")},
 97                             Locale.$STR("net.header.Domain")
 98                         )
 99                     ),
100                     TD({id: "netSizeCol", width: "4%", "class": "netHeaderCell a11yFocus",
101                         "role": "columnheader"},
102                         DIV({"class": "netHeaderCellBox",
103                             title: Locale.$STR("net.header.Size Tooltip")},
104                             Locale.$STR("net.header.Size")
105                         )
106                     ),
107                     TD({id: "netLocalAddressCol", width: "4%", "class": "netHeaderCell a11yFocus",
108                         "role": "columnheader"},
109                         DIV({"class": "netHeaderCellBox",
110                             title: Locale.$STR("net.header.Local IP Tooltip")},
111                             Locale.$STR("net.header.Local IP")
112                         )
113                     ),
114                     TD({id: "netRemoteAddressCol", width: "4%", "class": "netHeaderCell a11yFocus",
115                         "role": "columnheader"},
116                         DIV({"class": "netHeaderCellBox",
117                             title: Locale.$STR("net.header.Remote IP Tooltip")},
118                             Locale.$STR("net.header.Remote IP")
119                         )
120                     ),
121                     TD({id: "netTimeCol", width: "53%", "class": "netHeaderCell a11yFocus",
122                         "role": "columnheader"},
123                         DIV({"class": "netHeaderCellBox",
124                             title: Locale.$STR("net.header.Timeline Tooltip")},
125                             Locale.$STR("net.header.Timeline")
126                         )
127                     )
128                 )
129             ),
130             TBODY({"class": "netTableBody", "role" : "presentation"})
131         ),
132 
133     onClickHeader: function(event)
134     {
135         if (FBTrace.DBG_NET)
136             FBTrace.sysout("net.onClickHeader\n");
137 
138         // Also support enter key for sorting
139         if (!Events.isLeftClick(event) && !(event.type == "keypress" && event.keyCode == 13))
140             return;
141 
142         var table = Dom.getAncestorByClass(event.target, "netTable");
143         var column = Dom.getAncestorByClass(event.target, "netHeaderCell");
144         this.sortColumn(table, column);
145     },
146 
147     sortColumn: function(table, col, direction)
148     {
149         if (!col)
150             return;
151 
152         var numerical = !Css.hasClass(col, "alphaValue");
153 
154         var colIndex = 0;
155         for (col = col.previousSibling; col; col = col.previousSibling)
156             ++colIndex;
157 
158         // the first breakpoint bar column is not sortable.
159         if (colIndex == 0)
160             return;
161 
162         this.sort(table, colIndex, numerical, direction);
163     },
164 
165     sort: function(table, colIndex, numerical, direction)
166     {
167         var headerRow = table.querySelector(".netHeaderRow");
168 
169         // Remove class from the currently sorted column
170         var headerSorted = Dom.getChildByClass(headerRow, "netHeaderSorted");
171         Css.removeClass(headerSorted, "netHeaderSorted");
172         if (headerSorted)
173             headerSorted.removeAttribute("aria-sort");
174 
175         // Mark new column as sorted.
176         var header = headerRow.childNodes[colIndex];
177         Css.setClass(header, "netHeaderSorted");
178 
179         // If the column is already using required sort direction, bubble out.
180         if ((direction == "desc" && header.sorted == 1) ||
181             (direction == "asc" && header.sorted == -1))
182             return;
183 
184         var newDirection = ((header.sorted && header.sorted == 1) || (!header.sorted && direction == "asc")) ? "ascending" : "descending";
185         if (header)
186             header.setAttribute("aria-sort", newDirection);
187 
188         var tbody = table.lastChild;
189         var colID = header.getAttribute("id");
190 
191         table.setAttribute("sortcolumn", colID);
192         table.setAttribute("sortdirection", newDirection);
193 
194         var values = [];
195         for (var row = tbody.childNodes[1]; row; row = row.nextSibling)
196         {
197             if (!row.repObject)
198                 continue;
199 
200             if (Css.hasClass(row, "history"))
201                 continue;
202 
203             var cell = row.childNodes[colIndex];
204             var sortFunction = function sort(a, b) { return a.value < b.value ? -1 : 1; };
205             var ipSortFunction = function sort(a, b)
206             {
207                 var aParts = reSplitIP.exec(a.value);
208                 var bParts = reSplitIP.exec(b.value);
209 
210                 if (!aParts)
211                     return -1;
212                 if (!bParts)
213                     return 1;
214 
215                 for (var i=1; i<aParts.length; ++i)
216                 {
217                     if (parseInt(aParts[i]) != parseInt(bParts[i]))
218                         return parseInt(aParts[i]) < parseInt(bParts[i]) ? -1 : 1;
219                 }
220 
221                 return 1;
222             };
223             var value;
224 
225             switch (colID)
226             {
227                 case "netTimeCol":
228                     FBTrace.sysout("row.repObject", row.repObject);
229                     value = row.repObject.requestNumber;
230                     break;
231                 case "netSizeCol":
232                     value = row.repObject.size;
233                     break;
234                 case "netRemoteAddressCol":
235                 case "netLocalAddressCol":
236                     value = cell.textContent;
237                     sortFunction = ipSortFunction;
238                     break;
239                 default:
240                     value = numerical ? parseFloat(cell.textContent) : cell.textContent;
241             }
242 
243             if (Css.hasClass(row, "opened"))
244             {
245                 var netInfoRow = row.nextSibling;
246                 values.push({row: row, value: value, info: netInfoRow});
247                 row = netInfoRow;
248             }
249             else
250             {
251                 values.push({row: row, value: value});
252             }
253         }
254 
255         values.sort(sortFunction);
256 
257         if (newDirection == "ascending")
258         {
259             Css.removeClass(header, "sortedDescending");
260             Css.setClass(header, "sortedAscending");
261             header.sorted = -1;
262 
263             for (var i = 0; i < values.length; ++i)
264             {
265                 tbody.appendChild(values[i].row);
266                 if (values[i].info)
267                     tbody.appendChild(values[i].info);
268             }
269         }
270         else
271         {
272             Css.removeClass(header, "sortedAscending");
273             Css.setClass(header, "sortedDescending");
274 
275             header.sorted = 1;
276 
277             for (var i = values.length-1; i >= 0; --i)
278             {
279                 tbody.appendChild(values[i].row);
280                 if (values[i].info)
281                     tbody.appendChild(values[i].info);
282             }
283         }
284 
285         // Make sure the summary row is again at the end.
286         var summaryRow = tbody.getElementsByClassName("netSummaryRow").item(0);
287         tbody.appendChild(summaryRow);
288     },
289 
290     supportsObject: function(object, type)
291     {
292         return (object == this);
293     },
294 
295     /**
296      * Provides menu items for header context menu.
297      */
298     getContextMenuItems: function(object, target, context)
299     {
300         var popup = Firebug.chrome.$("fbContextMenu");
301         if (popup.firstChild && popup.firstChild.getAttribute("command") == "cmd_copy")
302             popup.removeChild(popup.firstChild);
303 
304         var items = [];
305 
306         // Iterate over all columns and create a menu item for each.
307         var table = context.getPanel(panelName, true).table;
308         var hiddenCols = table.getAttribute("hiddenCols");
309 
310         var lastVisibleIndex;
311         var visibleColCount = 0;
312 
313         // Iterate all columns except of the first one for breakpoints.
314         var header = Dom.getAncestorByClass(target, "netHeaderRow");
315         var columns = Arr.cloneArray(header.childNodes);
316         columns.shift();
317         for (var i=0; i<columns.length; i++)
318         {
319             var column = columns[i];
320             var columnContent = column.getElementsByClassName("netHeaderCellBox").item(0);
321             var visible = (hiddenCols.indexOf(column.id) == -1);
322 
323             items.push({
324                 label: columnContent.textContent,
325                 tooltiptext: columnContent.title,
326                 type: "checkbox",
327                 checked: visible,
328                 nol10n: true,
329                 command: Obj.bindFixed(this.onShowColumn, this, context, column.id)
330             });
331 
332             if (visible)
333             {
334                 lastVisibleIndex = i;
335                 visibleColCount++;
336             }
337         }
338 
339         // If the last column is visible, disable its menu item.
340         if (visibleColCount == 1)
341             items[lastVisibleIndex].disabled = true;
342 
343         items.push("-");
344         items.push({
345             label: "net.header.Reset_Header",
346             tooltiptext: "net.header.tip.Reset_Header",
347             command: Obj.bindFixed(this.onResetColumns, this, context)
348         });
349 
350         return items;
351     },
352 
353     onShowColumn: function(context, colId)
354     {
355         var panel = context.getPanel(panelName, true);
356         var table = panel.table;
357         var hiddenCols = table.getAttribute("hiddenCols");
358 
359         // If the column is already present in the list of hidden columns,
360         // remove it, otherwise append it.
361         var index = hiddenCols.indexOf(colId);
362         if (index >= 0)
363         {
364             table.setAttribute("hiddenCols", hiddenCols.substr(0,index-1) +
365                 hiddenCols.substr(index+colId.length));
366         }
367         else
368         {
369             table.setAttribute("hiddenCols", hiddenCols + " " + colId);
370         }
371 
372         // Store current state into the preferences.
373         Options.set(hiddenColsPref, table.getAttribute("hiddenCols"));
374 
375         panel.updateHRefLabelWidth();
376     },
377 
378     onResetColumns: function(context)
379     {
380         var panel = context.getPanel(panelName, true);
381         var header = panel.panelNode.getElementsByClassName("netHeaderRow").item(0);
382 
383         // Reset widths
384         var columns = header.childNodes;
385         for (var i=0; i<columns.length; i++)
386         {
387             var col = columns[i];
388             if (col.style)
389                 col.style.width = "";
390         }
391 
392         // Reset visibility. Only the Status column is hidden by default.
393         Options.clear(hiddenColsPref);
394         panel.table.setAttribute("hiddenCols", Options.get(hiddenColsPref));
395     },
396 });
397 
398 // ********************************************************************************************* //
399 
400 /**
401  * @domplate Represents a template that is used to render net panel entries.
402  */
403 Firebug.NetMonitor.NetRequestEntry = domplate(Firebug.Rep, new Firebug.Listener(),
404 {
405     fileTag:
406         FOR("file", "$files",
407             TR({"class": "netRow $file.file|getCategory focusRow outerFocusRow",
408                 onclick: "$onClick", "role": "row", "aria-expanded": "false",
409                 $hasHeaders: "$file.file|hasRequestHeaders",
410                 $history: "$file.file.history",
411                 $loaded: "$file.file.loaded",
412                 $responseError: "$file.file|isError",
413                 $fromBFCache: "$file.file|isFromBFCache",
414                 $fromCache: "$file.file.fromCache",
415                 $inFrame: "$file.file|getInFrame"},
416                 TD({"class": "netDebugCol netCol"},
417                    DIV({"class": "sourceLine netRowHeader",
418                    onclick: "$onClickRowHeader"},
419                         " "
420                    )
421                 ),
422                 TD({"class": "netHrefCol netCol a11yFocus", "role": "rowheader"},
423                     DIV({"class": "netHrefLabel netLabel",
424                          style: "margin-left: $file.file|getIndent\\px"},
425                         "$file.file|getHref"
426                     ),
427                     DIV({"class": "netFullHrefLabel netHrefLabel",
428                          style: "margin-left: $file.file|getIndent\\px"},
429                         "$file.file.href"
430                     )
431                 ),
432                 TD({"class": "netStatusCol netCol a11yFocus", "role": "gridcell"},
433                     DIV({"class": "netStatusLabel netLabel"}, "$file.file|getStatus")
434                 ),
435                 TD({"class": "netProtocolCol netCol a11yFocus", "role": "gridcell"},
436                     DIV({"class": "netProtocolLabel netLabel"}, "$file.file|getProtocol")
437                 ),
438                 TD({"class": "netDomainCol netCol a11yFocus", "role": "gridcell" },
439                     DIV({"class": "netDomainLabel netLabel"}, "$file.file|getDomain")
440                 ),
441                 TD({"class": "netSizeCol netCol a11yFocus", "role": "gridcell",
442                     "aria-describedby": "fbNetSizeInfoTip"},
443                     DIV({"class": "netSizeLabel netLabel"}, "$file.file|getSize")
444                 ),
445                 TD({"class": "netLocalAddressCol netCol a11yFocus", "role": "gridcell"},
446                     DIV({"class": "netAddressLabel netLabel"}, "$file.file|getLocalAddress")
447                 ),
448                 TD({"class": "netRemoteAddressCol netCol a11yFocus", "role": "gridcell"},
449                     DIV({"class": "netAddressLabel netLabel"}, "$file.file|getRemoteAddress")
450                 ),
451                 TD({"class": "netTimeCol netCol a11yFocus", "role": "gridcell",
452                     "aria-describedby": "fbNetTimeInfoTip"  },
453                     DIV({"class": "netLoadingIcon"}),
454                     DIV({"class": "netBar"},
455                         " ",
456                         DIV({"class": "netBlockingBar", style: "left: $file.offset"}),
457                         DIV({"class": "netResolvingBar", style: "left: $file.offset"}),
458                         DIV({"class": "netConnectingBar", style: "left: $file.offset"}),
459                         DIV({"class": "netSendingBar", style: "left: $file.offset"}),
460                         DIV({"class": "netWaitingBar", style: "left: $file.offset"}),
461                         DIV({"class": "netReceivingBar", style: "left: $file.offset; width: $file.width"},
462                             SPAN({"class": "netTimeLabel"}, "$file|getElapsedTime")
463                         )
464                         // Page timings (vertical lines) are dynamically appended here.
465                     )
466                 )
467             )
468         ),
469 
470     netInfoTag:
471         TR({"class": "netInfoRow $file|getCategory outerFocusRow", "role" : "row"},
472             TD({"class": "sourceLine netRowHeader"}),
473             TD({"class": "netInfoCol", colspan: 8, "role" : "gridcell"})
474         ),
475 
476     activationTag:
477         TR({"class": "netRow netActivationRow"},
478             TD({"class": "netCol netActivationLabel", colspan: 9, "role": "status"},
479                 Locale.$STR("net.ActivationMessage")
480             )
481         ),
482 
483     summaryTag:
484         TR({"class": "netRow netSummaryRow focusRow outerFocusRow", "role": "row",
485             "aria-live": "polite"},
486             TD({"class": "netCol"}, " "),
487             TD({"class": "netCol netHrefCol a11yFocus", "role" : "rowheader"},
488                 DIV({"class": "netCountLabel netSummaryLabel"}, "-")
489             ),
490             TD({"class": "netCol netStatusCol a11yFocus", "role" : "gridcell"}),
491             TD({"class": "netCol netProtocolCol a11yFocus", "role" : "gridcell"}),
492             TD({"class": "netCol netDomainCol a11yFocus", "role" : "gridcell"}),
493             TD({"class": "netTotalSizeCol netCol netSizeCol a11yFocus", "role": "gridcell"},
494                 DIV({"class": "netTotalSizeLabel netSummaryLabel"}, "0 B")
495             ),
496             TD({"class": "netTotalTimeCol netCol netTimeCol a11yFocus", "role":
497                 "gridcell", colspan: "3"},
498                 DIV({"class": "netSummaryBar", style: "width: 100%"},
499                     DIV({"class": "netCacheSizeLabel netSummaryLabel", collapsed: "true"},
500                         "(",
501                         SPAN("0 B"),
502                         SPAN(" " + Locale.$STR("FromCache")),
503                         ")"
504                     ),
505                     DIV({"class": "netTimeBar"},
506                         SPAN({"class": "netTotalTimeLabel netSummaryLabel"}, "0ms")
507                     )
508                 )
509             )
510         ),
511 
512     footerTag:
513         TR({"class": "netFooterRow", "style" : "height: 100%"},
514             TD({"class": "", colspan: 9})
515         ),
516 
517     onClickRowHeader: function(event)
518     {
519         Events.cancelEvent(event);
520 
521         var rowHeader = event.target;
522         if (!Css.hasClass(rowHeader, "netRowHeader"))
523             return;
524 
525         var row = Dom.getAncestorByClass(event.target, "netRow");
526         if (!row)
527             return;
528 
529         var context = Firebug.getElementPanel(row).context;
530         var panel = context.getPanel(panelName, true);
531         if (panel)
532             panel.breakOnRequest(row.repObject);
533     },
534 
535     onClick: function(event)
536     {
537         if (Events.isLeftClick(event))
538         {
539             var row = Dom.getAncestorByClass(event.target, "netRow");
540             if (row)
541             {
542                 // Click on the rowHeader element inserts a breakpoint.
543                 if (Dom.getAncestorByClass(event.target, "netRowHeader"))
544                     return;
545 
546                 this.toggleHeadersRow(row);
547                 Events.cancelEvent(event);
548             }
549         }
550     },
551 
552     toggleHeadersRow: function(row)
553     {
554         if (!Css.hasClass(row, "hasHeaders"))
555             return;
556 
557         var file = row.repObject;
558 
559         Css.toggleClass(row, "opened");
560         if (Css.hasClass(row, "opened"))
561         {
562             var netInfoRow = this.netInfoTag.insertRows({file: file}, row)[0];
563             var netInfoCol = netInfoRow.getElementsByClassName("netInfoCol").item(0);
564             var netInfoBox = Firebug.NetMonitor.NetInfoBody.tag.replace({file: file}, netInfoCol);
565 
566             // Notify listeners so additional tabs can be created.
567             Events.dispatch(Firebug.NetMonitor.NetInfoBody.fbListeners, "initTabBody",
568                 [netInfoBox, file]);
569 
570             // Select "Headers" tab by default, if no other tab is selected already.
571             // (e.g. by a third party Firebug extension in 'initTabBody' event)
572             if (!netInfoBox.selectedTab)
573                 Firebug.NetMonitor.NetInfoBody.selectTabByName(netInfoBox, "Headers");
574 
575             var category = NetUtils.getFileCategory(row.repObject);
576             if (category)
577                 Css.setClass(netInfoBox, "category-" + category);
578             row.setAttribute('aria-expanded', 'true');
579         }
580         else
581         {
582             var netInfoRow = row.nextSibling;
583             var netInfoBox = netInfoRow.getElementsByClassName("netInfoBody").item(0);
584 
585             Events.dispatch(Firebug.NetMonitor.NetInfoBody.fbListeners, "destroyTabBody",
586                 [netInfoBox, file]);
587 
588             row.parentNode.removeChild(netInfoRow);
589             row.setAttribute('aria-expanded', 'false');
590         }
591     },
592 
593     getCategory: function(file)
594     {
595         var category = NetUtils.getFileCategory(file);
596         if (category)
597             return "category-" + category;
598 
599         return "category-undefined";
600     },
601 
602     getInFrame: function(file)
603     {
604         return !!(file.document ? file.document.parent : false);
605     },
606 
607     getIndent: function(file)
608     {
609         // XXXjoe Turn off indenting for now, it's confusing since we don't
610         // actually place nested files directly below their parent
611         //return file.document.level * indentWidth;
612         return 10;
613     },
614 
615     isNtlmAuthorizationRequest: function(file)
616     {
617         if (file.responseStatus != 401)
618             return false;
619 
620         //xxxsz: file.responseHeaders is undefined here for some reason
621         var resp = file.responseHeadersText.match(/www-authenticate:\s(.+)/i)[1];
622         return (resp && resp.search(/ntlm|negotiate/i) >= 0);
623     },
624 
625     isError: function(file)
626     {
627         if (file.aborted)
628             return true;
629 
630         if (this.isNtlmAuthorizationRequest(file))
631             return false;
632 
633         var errorRange = Math.floor(file.responseStatus/100);
634         return errorRange == 4 || errorRange == 5;
635     },
636 
637     isFromBFCache: function(file)
638     {
639         return file.fromBFCache;
640     },
641 
642     getHref: function(file)
643     {
644         var fileName = Url.getFileName(file.href);
645         var limit = Options.get("stringCropLength");
646         if (limit > 0)
647             fileName = Str.cropString(fileName, limit);
648         return (file.method ? file.method.toUpperCase() : "?") + " " + fileName;
649     },
650 
651     getProtocol: function(file)
652     {
653         var protocol = Url.getProtocol(file.href);
654         var text = file.responseHeadersText;
655         var spdy = text ? text.search(/X-Firefox-Spdy/i) >= 0 : null;
656         return spdy ? protocol + " SPDY" : protocol;
657     },
658 
659     getStatus: function(file)
660     {
661         var text = "";
662 
663         if (file.responseStatus)
664             text += file.responseStatus + " ";
665 
666         if (file.responseStatusText)
667             text += file.responseStatusText;
668 
669         text = text ? Str.cropString(text) : " ";
670 
671         if (file.fromAppCache)
672             text += " (AppCache)";
673         else if (file.fromBFCache)
674             text += " (BFCache)";
675 
676         return text
677     },
678 
679     getDomain: function(file)
680     {
681         return Url.getPrettyDomain(file.href);
682     },
683 
684     getSize: function(file)
685     {
686         var size = (file.size >= 0) ? file.size : 0;
687         return this.formatSize(size);
688     },
689 
690     getLocalAddress: function(file)
691     {
692         return Str.formatIP(file.localAddress, file.localPort);
693     },
694 
695     getRemoteAddress: function(file)
696     {
697         return Str.formatIP(file.remoteAddress, file.remotePort);
698     },
699 
700     getElapsedTime: function(file)
701     {
702         if (!file.elapsed || file.elapsed < 0)
703             return "";
704 
705         return this.formatTime(file.elapsed);
706     },
707 
708     hasRequestHeaders: function(file)
709     {
710         return !!file.requestHeaders;
711     },
712 
713     formatSize: function(bytes)
714     {
715         return Str.formatSize(bytes);
716     },
717 
718     formatTime: function(elapsed)
719     {
720         return Str.formatTime(elapsed);
721     }
722 });
723 
724 // ********************************************************************************************* //
725 
726 Firebug.NetMonitor.NetPage = domplate(Firebug.Rep,
727 {
728     separatorTag:
729         TR({"class": "netRow netPageSeparatorRow"},
730             TD({"class": "netCol netPageSeparatorLabel", colspan: 8, "role": "separator"})
731         ),
732 
733     pageTag:
734         TR({"class": "netRow netPageRow", onclick: "$onPageClick"},
735             TD({"class": "netCol netPageCol", colspan: 8, "role": "separator"},
736                 DIV({"class": "netLabel netPageLabel netPageTitle"}, "$page|getTitle")
737             )
738         ),
739 
740     getTitle: function(page)
741     {
742         return page.pageTitle;
743     },
744 
745     onPageClick: function(event)
746     {
747         if (!Events.isLeftClick(event))
748             return;
749 
750         var target = event.target;
751         var pageRow = Dom.getAncestorByClass(event.target, "netPageRow");
752         var panel = Firebug.getElementPanel(pageRow);
753 
754         if (!Css.hasClass(pageRow, "opened"))
755         {
756             Css.setClass(pageRow, "opened");
757 
758             var files = pageRow.files;
759 
760             // Move all net-rows from the persistedState to this panel.
761             panel.insertRows(files, pageRow);
762 
763             for (var i=0; i<files.length; i++)
764                 panel.queue.push(files[i].file);
765 
766             panel.layout();
767         }
768         else
769         {
770             Css.removeClass(pageRow, "opened");
771 
772             var nextRow = pageRow.nextSibling;
773             while (!Css.hasClass(nextRow, "netPageRow") &&
774                 !Css.hasClass(nextRow, "netPageSeparatorRow"))
775             {
776                 var nextSibling = nextRow.nextSibling;
777                 nextRow.parentNode.removeChild(nextRow);
778                 nextRow = nextSibling;
779             }
780         }
781     },
782 });
783 
784 // ********************************************************************************************* //
785 
786 /**
787  * @domplate Represents a template that is used to render detailed info about a request.
788  * This template is rendered when a request is expanded.
789  */
790 Firebug.NetMonitor.NetInfoBody = domplate(Firebug.Rep, new Firebug.Listener(),
791 {
792     tag:
793         DIV({"class": "netInfoBody", _repObject: "$file"},
794             TAG("$infoTabs", {file: "$file"}),
795             TAG("$infoBodies", {file: "$file"})
796         ),
797 
798     infoTabs:
799         DIV({"class": "netInfoTabs focusRow subFocusRow", "role": "tablist"},
800             A({"class": "netInfoParamsTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
801                 view: "Params",
802                 $collapsed: "$file|hideParams"},
803                 Locale.$STR("URLParameters")
804             ),
805             A({"class": "netInfoHeadersTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
806                 view: "Headers"},
807                 Locale.$STR("Headers")
808             ),
809             A({"class": "netInfoPostTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
810                 view: "Post",
811                 $collapsed: "$file|hidePost"},
812                 Locale.$STR("Post")
813             ),
814             A({"class": "netInfoPutTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
815                 view: "Put",
816                 $collapsed: "$file|hidePut"},
817                 Locale.$STR("Put")
818             ),
819             A({"class": "netInfoResponseTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
820                 view: "Response",
821                 $collapsed: "$file|hideResponse"},
822                 Locale.$STR("Response")
823             ),
824             A({"class": "netInfoCacheTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
825                view: "Cache",
826                $collapsed: "$file|hideCache"},
827                Locale.$STR("Cache")
828             ),
829             A({"class": "netInfoHtmlTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
830                view: "Html",
831                $collapsed: "$file|hideHtml"},
832                Locale.$STR("HTML")
833             )
834         ),
835 
836     infoBodies:
837         DIV({"class": "netInfoBodies outerFocusRow"},
838             TABLE({"class": "netInfoParamsText netInfoText netInfoParamsTable", "role": "tabpanel",
839                     cellpadding: 0, cellspacing: 0}, TBODY()),
840             DIV({"class": "netInfoHeadersText netInfoText", "role": "tabpanel"}),
841             DIV({"class": "netInfoPostText netInfoText", "role": "tabpanel"}),
842             DIV({"class": "netInfoPutText netInfoText", "role": "tabpanel"}),
843             DIV({"class": "netInfoResponseText netInfoText", "role": "tabpanel"}),
844             DIV({"class": "netInfoCacheText netInfoText", "role": "tabpanel"},
845                 TABLE({"class": "netInfoCacheTable", cellpadding: 0, cellspacing: 0,
846                     "role": "presentation"},
847                     TBODY({"role": "list", "aria-label": Locale.$STR("Cache")})
848                 )
849             ),
850             DIV({"class": "netInfoHtmlText netInfoText", "role": "tabpanel"},
851                 IFRAME({"class": "netInfoHtmlPreview", "role": "document"}),
852                 DIV({"class": "htmlPreviewResizer"})
853             )
854         ),
855 
856     headerDataTag:
857         FOR("param", "$headers",
858             TR({"role": "listitem"},
859                 TD({"class": "netInfoParamName", "role": "presentation"},
860                     TAG("$param|getNameTag", {param: "$param"})
861                 ),
862                 TD({"class": "netInfoParamValue", "role": "list", "aria-label": "$param.name"},
863                     FOR("line", "$param|getParamValueIterator",
864                         CODE({"class": "focusRow subFocusRow", "role": "listitem"}, "$line")
865                     )
866                 )
867             )
868         ),
869 
870     responseHeadersFromBFCacheTag:
871         TR(
872             TD({"class": "headerFromBFCache"},
873                 Locale.$STR("net.label.ResponseHeadersFromBFCache")
874             )
875         ),
876 
877     customTab:
878         A({"class": "netInfo$tabId\\Tab netInfoTab", onclick: "$onClickTab",
879             view: "$tabId", "role": "tab"},
880             "$tabTitle"
881         ),
882 
883     customBody:
884         DIV({"class": "netInfo$tabId\\Text netInfoText", "role": "tabpanel"}),
885 
886     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
887 
888     nameTag:
889         SPAN("$param|getParamName"),
890 
891     nameWithTooltipTag:
892         SPAN({title: "$param.name"}, "$param|getParamName"),
893 
894     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
895 
896     getNameTag: function(param)
897     {
898         return (this.getParamName(param) == param.name) ? this.nameTag : this.nameWithTooltipTag;
899     },
900 
901     getParamName: function(param)
902     {
903         var name = param.name;
904         var limit = Firebug.netParamNameLimit;
905         if (limit <= 0)
906             return name;
907 
908         if (name.length > limit)
909             name = name.substr(0, limit) + "...";
910         return name;
911     },
912 
913     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
914 
915     hideParams: function(file)
916     {
917         return !file.urlParams || !file.urlParams.length;
918     },
919 
920     hidePost: function(file)
921     {
922         return file.method.toUpperCase() != "POST";
923     },
924 
925     hidePut: function(file)
926     {
927         return file.method.toUpperCase() != "PUT";
928     },
929 
930     hideResponse: function(file)
931     {
932         var headers = file.responseHeaders;
933         for (var i=0; headers && i<headers.length; i++)
934         {
935             if (headers[i].name == "Content-Length")
936                 return headers[i].value == 0;
937         }
938 
939         return file.category in NetUtils.binaryFileCategories || file.responseText == "";
940     },
941 
942     hideCache: function(file)
943     {
944         //xxxHonza: I don't see any reason why not to display the cache info also for images.
945         return !file.cacheEntry/* || file.category=="image"*/;
946     },
947 
948     hideHtml: function(file)
949     {
950         if (!file.mimeType)
951             return true;
952 
953         var types = ["text/html", "application/xhtml+xml"];
954         return !NetUtils.matchesContentType(file.mimeType, types);
955     },
956 
957     onClickTab: function(event)
958     {
959         this.selectTab(event.currentTarget);
960     },
961 
962     getParamValueIterator: function(param)
963     {
964         // This value is inserted into CODE element and so, make sure the HTML isn't escaped (1210).
965         // This is why the second parameter is true.
966         // The CODE (with style white-space:pre) element preserves whitespaces so they are
967         // displayed the same, as they come from the server (1194).
968         // In case of a long header values of post parameters the value must be wrapped (2105).
969         return Str.wrapText(param.value, true);
970     },
971 
972     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
973 
974     appendTab: function(netInfoBox, tabId, tabTitle)
975     {
976         // Create new tab and body.
977         var args = {tabId: tabId, tabTitle: tabTitle};
978         this.customTab.append(args, netInfoBox.getElementsByClassName("netInfoTabs").item(0));
979         this.customBody.append(args, netInfoBox.getElementsByClassName("netInfoBodies").item(0));
980     },
981 
982     selectTabByName: function(netInfoBox, tabName)
983     {
984         var tab = Dom.getChildByClass(netInfoBox, "netInfoTabs", "netInfo" + tabName + "Tab");
985         if (!tab)
986             return false;
987 
988         this.selectTab(tab);
989 
990         return true;
991     },
992 
993     selectTab: function(tab)
994     {
995         var netInfoBox = Dom.getAncestorByClass(tab, "netInfoBody");
996 
997         var view = tab.getAttribute("view");
998         if (netInfoBox.selectedTab)
999         {
1000             netInfoBox.selectedTab.removeAttribute("selected");
1001             netInfoBox.selectedText.removeAttribute("selected");
1002             netInfoBox.selectedTab.setAttribute("aria-selected", "false");
1003         }
1004 
1005         var textBodyName = "netInfo" + view + "Text";
1006 
1007         netInfoBox.selectedTab = tab;
1008         netInfoBox.selectedText = netInfoBox.getElementsByClassName(textBodyName).item(0);
1009 
1010         netInfoBox.selectedTab.setAttribute("selected", "true");
1011         netInfoBox.selectedText.setAttribute("selected", "true");
1012         netInfoBox.selectedTab.setAttribute("aria-selected", "true");
1013 
1014         var file = Firebug.getRepObject(netInfoBox);
1015         var panel = Firebug.getElementPanel(netInfoBox);
1016         if (!panel)
1017         {
1018             if (FBTrace.DBG_ERRORS)
1019                 FBTrace.sysout("net.selectTab; ERROR no panel");
1020             return;
1021         }
1022 
1023         var context = panel.context;
1024         this.updateInfo(netInfoBox, file, context);
1025     },
1026 
1027     updateInfo: function(netInfoBox, file, context)
1028     {
1029         if (FBTrace.DBG_NET)
1030             FBTrace.sysout("net.updateInfo; file", file);
1031 
1032         if (!netInfoBox)
1033         {
1034             if (FBTrace.DBG_NET || FBTrace.DBG_ERRORS)
1035                 FBTrace.sysout("net.updateInfo; ERROR netInfo == null " + file.href, file);
1036             return;
1037         }
1038 
1039         var tab = netInfoBox.selectedTab;
1040         if (Css.hasClass(tab, "netInfoParamsTab"))
1041         {
1042             if (file.urlParams && !netInfoBox.urlParamsPresented)
1043             {
1044                 netInfoBox.urlParamsPresented = true;
1045                 this.insertHeaderRows(netInfoBox, file.urlParams, "Params");
1046             }
1047         }
1048 
1049         if (Css.hasClass(tab, "netInfoHeadersTab"))
1050         {
1051             var headersText = netInfoBox.getElementsByClassName("netInfoHeadersText").item(0);
1052 
1053             if (file.responseHeaders && !netInfoBox.responseHeadersPresented)
1054             {
1055                 netInfoBox.responseHeadersPresented = true;
1056 
1057                 Firebug.NetMonitor.NetInfoHeaders.renderHeaders(headersText,
1058                     file.responseHeaders, "ResponseHeaders");
1059 
1060                 // If the request comes from the BFCache do not display reponse headers.
1061                 // There is not real response from the server and all headers come from
1062                 // the cache. So, the user should see the 'Response Headers From Cache'
1063                 // section (see issue 5573).
1064                 if (file.fromBFCache)
1065                 {
1066                     // Display a message instead of headers.
1067                     var body = Dom.getElementByClass(headersText, "netInfoResponseHeadersBody");
1068                     Firebug.NetMonitor.NetInfoBody.responseHeadersFromBFCacheTag.replace({}, body);
1069                 }
1070             }
1071 
1072             if (file.cachedResponseHeaders && !netInfoBox.cachedResponseHeadersPresented)
1073             {
1074                 netInfoBox.cachedResponseHeadersPresented = true;
1075                 Firebug.NetMonitor.NetInfoHeaders.renderHeaders(headersText,
1076                     file.cachedResponseHeaders, "CachedResponseHeaders");
1077             }
1078 
1079             if (file.requestHeaders && !netInfoBox.requestHeadersPresented)
1080             {
1081                 netInfoBox.requestHeadersPresented = true;
1082                 Firebug.NetMonitor.NetInfoHeaders.renderHeaders(headersText,
1083                     file.requestHeaders, "RequestHeaders");
1084             }
1085 
1086             if (!file.postRequestsHeaders)
1087             {
1088                 var text = NetUtils.getPostText(file, context, true);
1089                 file.postRequestsHeaders = Http.getHeadersFromPostText(file.request, text);
1090             }
1091 
1092             if (file.postRequestsHeaders && !netInfoBox.postRequestsHeadersPresented)
1093             {
1094                 netInfoBox.postRequestsHeadersPresented = true;
1095                 Firebug.NetMonitor.NetInfoHeaders.renderHeaders(headersText,
1096                     file.postRequestsHeaders, "PostRequestHeaders");
1097             }
1098         }
1099 
1100         if (Css.hasClass(tab, "netInfoPostTab"))
1101         {
1102             if (!netInfoBox.postPresented)
1103             {
1104                 netInfoBox.postPresented  = true;
1105                 var postText = netInfoBox.getElementsByClassName("netInfoPostText").item(0);
1106                 Firebug.NetMonitor.NetInfoPostData.render(context, postText, file);
1107             }
1108         }
1109 
1110         if (Css.hasClass(tab, "netInfoPutTab"))
1111         {
1112             if (!netInfoBox.putPresented)
1113             {
1114                 netInfoBox.putPresented  = true;
1115                 var putText = netInfoBox.getElementsByClassName("netInfoPutText").item(0);
1116                 Firebug.NetMonitor.NetInfoPostData.render(context, putText, file);
1117             }
1118         }
1119 
1120         if (Css.hasClass(tab, "netInfoResponseTab") && file.loaded && !netInfoBox.responsePresented)
1121         {
1122             var responseTextBox = netInfoBox.getElementsByClassName("netInfoResponseText").item(0);
1123 
1124             // Let listeners display the response
1125             Events.dispatch(this.fbListeners, "updateResponse", [netInfoBox, file, context]);
1126 
1127             if (FBTrace.DBG_NET)
1128                 FBTrace.sysout("netInfoResponseTab", {netInfoBox: netInfoBox, file: file});
1129             if (!netInfoBox.responsePresented)
1130             {
1131                 if (file.category == "image")
1132                 {
1133                     netInfoBox.responsePresented = true;
1134     
1135                     var responseImage = netInfoBox.ownerDocument.createElement("img");
1136                     responseImage.src = file.href;
1137     
1138                     Dom.clearNode(responseTextBox);
1139                     responseTextBox.appendChild(responseImage, responseTextBox);
1140                 }
1141                 else if (!(NetUtils.binaryCategoryMap.hasOwnProperty(file.category)))
1142                 {
1143                     this.setResponseText(file, netInfoBox, responseTextBox, context);
1144                 }
1145             }
1146         }
1147 
1148         if (Css.hasClass(tab, "netInfoCacheTab") && file.loaded && !netInfoBox.cachePresented)
1149         {
1150             var responseTextBox = netInfoBox.getElementsByClassName("netInfoCacheText").item(0);
1151             if (file.cacheEntry) {
1152                 netInfoBox.cachePresented = true;
1153                 this.insertHeaderRows(netInfoBox, file.cacheEntry, "Cache");
1154             }
1155         }
1156 
1157         if (Css.hasClass(tab, "netInfoHtmlTab") && file.loaded && !netInfoBox.htmlPresented)
1158         {
1159             netInfoBox.htmlPresented = true;
1160 
1161             var text = NetUtils.getResponseText(file, context);
1162             this.htmlPreview = netInfoBox.getElementsByClassName("netInfoHtmlPreview").item(0);
1163             this.htmlPreview.contentWindow.document.body.innerHTML = text;
1164 
1165             // Workaround for issue 5774 (it's not clear why the 'load' event is actually
1166             // sent to the iframe when the user swithes Firebug panels).
1167             // The event is sent only for the iframes in the Console panel.
1168             context.addEventListener(this.htmlPreview, "load", function(event)
1169             {
1170                 event.target.contentDocument.body.innerHTML = text;
1171             });
1172 
1173             var defaultHeight = parseInt(Options.get("netHtmlPreviewHeight"));
1174             if (!isNaN(defaultHeight))
1175                 this.htmlPreview.style.height = defaultHeight + "px";
1176 
1177             var handler = netInfoBox.querySelector(".htmlPreviewResizer");
1178             this.resizer = new DragDrop.Tracker(handler, {
1179                 onDragStart: Obj.bind(this.onDragStart, this),
1180                 onDragOver: Obj.bind(this.onDragOver, this),
1181                 onDrop: Obj.bind(this.onDrop, this)
1182             });
1183         }
1184 
1185         // Notify listeners about update so, content of custom tabs can be updated.
1186         Events.dispatch(Firebug.NetMonitor.NetInfoBody.fbListeners, "updateTabBody",
1187             [netInfoBox, file, context]);
1188     },
1189 
1190     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1191     // HTML Preview Resizer
1192 
1193     onDragStart: function(tracker)
1194     {
1195         var body = Dom.getBody(this.htmlPreview.ownerDocument);
1196         body.setAttribute("resizingHtmlPreview", "true");
1197         this.startHeight = this.htmlPreview.clientHeight;
1198     },
1199 
1200     onDragOver: function(newPos, tracker)
1201     {
1202         var newHeight = (this.startHeight + newPos.y);
1203         this.htmlPreview.style.height = newHeight + "px";
1204         Options.setPref(Firebug.prefDomain, "netHtmlPreviewHeight", newHeight);
1205     },
1206 
1207     onDrop: function(tracker)
1208     {
1209         var body = Dom.getBody(this.htmlPreview.ownerDocument);
1210         body.removeAttribute("resizingHtmlPreview");
1211     },
1212 
1213     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1214 
1215     setResponseText: function(file, netInfoBox, responseTextBox, context)
1216     {
1217         // Get response text and make sure it doesn't exceed the max limit.
1218         var text = NetUtils.getResponseText(file, context);
1219         var limit = Firebug.netDisplayedResponseLimit + 15;
1220         var limitReached = text ? (text.length > limit) : false;
1221         if (limitReached)
1222             text = text.substr(0, limit) + "...";
1223 
1224         // Insert the response into the UI.
1225         if (text)
1226             Str.insertWrappedText(text, responseTextBox);
1227         else
1228             Str.insertWrappedText("", responseTextBox);
1229 
1230         // Append a message informing the user that the response isn't fully displayed.
1231         if (limitReached)
1232         {
1233             var object = {
1234                 text: Locale.$STR("net.responseSizeLimitMessage"),
1235                 onClickLink: function() {
1236                     NetUtils.openResponseInTab(file);
1237                 }
1238             };
1239             Firebug.NetMonitor.ResponseSizeLimit.append(object, responseTextBox);
1240         }
1241 
1242         netInfoBox.responsePresented = true;
1243 
1244         if (FBTrace.DBG_NET)
1245             FBTrace.sysout("net.setResponseText; response text updated");
1246     },
1247 
1248     insertHeaderRows: function(netInfoBox, headers, tableName, rowName)
1249     {
1250         if (!headers.length)
1251             return;
1252 
1253         var headersTable = netInfoBox.getElementsByClassName("netInfo"+tableName+"Table").item(0);
1254         var tbody = Dom.getChildByClass(headersTable, "netInfo" + rowName + "Body");
1255         if (!tbody)
1256             tbody = headersTable.firstChild;
1257         var titleRow = Dom.getChildByClass(tbody, "netInfo" + rowName + "Title");
1258 
1259         headers.sort(function(a, b)
1260         {
1261             return a.name > b.name ? 1 : -1;
1262         });
1263 
1264         this.headerDataTag.insertRows({headers: headers}, titleRow ? titleRow : tbody);
1265         Css.removeClass(titleRow, "collapsed");
1266     },
1267 });
1268 
1269 // ********************************************************************************************* //
1270 
1271 /**
1272  * @domplate Represents posted data within request info (the info, which is visible when
1273  * a request entry is expanded. This template renders content of the Post tab.
1274  */
1275 Firebug.NetMonitor.NetInfoPostData = domplate(Firebug.Rep, new Firebug.Listener(),
1276 {
1277     // application/x-www-form-urlencoded
1278     paramsTable:
1279         TABLE({"class": "netInfoPostParamsTable", cellpadding: 0, cellspacing: 0,
1280             "role": "presentation"},
1281             TBODY({"role": "list", "aria-label": Locale.$STR("net.label.Parameters")},
1282                 TR({"class": "netInfoPostParamsTitle", "role": "presentation"},
1283                     TD({colspan: 2, "role": "presentation"},
1284                         DIV({"class": "netInfoPostParams"},
1285                             Locale.$STR("net.label.Parameters"),
1286                             SPAN({"class": "netInfoPostContentType"},
1287                                 "application/x-www-form-urlencoded"
1288                             )
1289                         )
1290                     )
1291                 )
1292             )
1293         ),
1294 
1295     // multipart/form-data
1296     partsTable:
1297         TABLE({"class": "netInfoPostPartsTable", cellpadding: 0, cellspacing: 0,
1298             "role": "presentation"},
1299             TBODY({"role": "list", "aria-label": Locale.$STR("net.label.Parts")},
1300                 TR({"class": "netInfoPostPartsTitle", "role": "presentation"},
1301                     TD({colspan: 2, "role":"presentation" },
1302                         DIV({"class": "netInfoPostParams"},
1303                             Locale.$STR("net.label.Parts"),
1304                             SPAN({"class": "netInfoPostContentType"},
1305                                 "multipart/form-data"
1306                             )
1307                         )
1308                     )
1309                 )
1310             )
1311         ),
1312 
1313     // application/json
1314     jsonTable:
1315         TABLE({"class": "netInfoPostJSONTable", cellpadding: 0, cellspacing: 0,
1316             "role": "presentation"},
1317             TBODY({"role": "list", "aria-label": Locale.$STR("jsonviewer.tab.JSON")},
1318                 TR({"class": "netInfoPostJSONTitle", "role": "presentation"},
1319                     TD({"role": "presentation" },
1320                         DIV({"class": "netInfoPostParams"},
1321                             Locale.$STR("jsonviewer.tab.JSON")
1322                         )
1323                     )
1324                 ),
1325                 TR(
1326                     TD({"class": "netInfoPostJSONBody"})
1327                 )
1328             )
1329         ),
1330 
1331     // application/xml
1332     xmlTable:
1333         TABLE({"class": "netInfoPostXMLTable", cellpadding: 0, cellspacing: 0,
1334             "role": "presentation"},
1335             TBODY({"role": "list", "aria-label": Locale.$STR("xmlviewer.tab.XML")},
1336                 TR({"class": "netInfoPostXMLTitle", "role": "presentation"},
1337                     TD({"role": "presentation" },
1338                         DIV({"class": "netInfoPostParams"},
1339                             Locale.$STR("xmlviewer.tab.XML")
1340                         )
1341                     )
1342                 ),
1343                 TR(
1344                     TD({"class": "netInfoPostXMLBody"})
1345                 )
1346             )
1347         ),
1348 
1349     // image/svg+xml
1350     svgTable:
1351         TABLE({"class": "netInfoPostSVGTable", cellpadding: 0, cellspacing: 0,
1352             "role": "presentation"},
1353             TBODY({"role": "list", "aria-label": Locale.$STR("svgviewer.tab.SVG")},
1354                 TR({"class": "netInfoPostSVGTitle", "role": "presentation"},
1355                     TD({"role": "presentation" },
1356                         DIV({"class": "netInfoPostParams"},
1357                             Locale.$STR("svgviewer.tab.SVG")
1358                         )
1359                     )
1360                 ),
1361                 TR(
1362                     TD({"class": "netInfoPostSVGBody"})
1363                 )
1364             )
1365         ),
1366 
1367     // application/x-woff
1368     fontTable:
1369       TABLE({"class": "netInfoPostFontTable", cellpadding: 0, cellspacing: 0,
1370         "role": "presentation"},
1371           TBODY({"role": "list", "aria-label": Locale.$STR("fontviewer.tab.Font")},
1372               TR({"class": "netInfoPostFontTitle", "role": "presentation"},
1373                   TD({"role": "presentation" },
1374                       Locale.$STR("fontviewer.tab.Font")
1375                   )
1376               ),
1377               TR(
1378                   TD({"class": "netInfoPostFontBody"})
1379               )
1380           )
1381       ),
1382 
1383     sourceTable:
1384         TABLE({"class": "netInfoPostSourceTable", cellpadding: 0, cellspacing: 0,
1385             "role": "presentation"},
1386             TBODY({"role": "list", "aria-label": Locale.$STR("net.label.Source")},
1387                 TR({"class": "netInfoPostSourceTitle", "role": "presentation"},
1388                     TD({colspan: 2, "role": "presentation"},
1389                         DIV({"class": "netInfoPostSource"},
1390                             Locale.$STR("net.label.Source")
1391                         )
1392                     )
1393                 )
1394             )
1395         ),
1396 
1397     sourceBodyTag:
1398         TR({"role": "presentation"},
1399             TD({colspan: 2, "role": "presentation"},
1400                 FOR("line", "$param|getParamValueIterator",
1401                     CODE({"class":"focusRow subFocusRow", "role": "listitem"}, "$line")
1402                 )
1403             )
1404         ),
1405 
1406     getParamValueIterator: function(param)
1407     {
1408         return Firebug.NetMonitor.NetInfoBody.getParamValueIterator(param);
1409     },
1410 
1411     render: function(context, parentNode, file)
1412     {
1413         var text = NetUtils.getPostText(file, context, true);
1414         if (text == undefined)
1415             return;
1416 
1417         if (NetUtils.isURLEncodedRequest(file, context))
1418         {
1419             var lines = text.split("\n");
1420             var params = Url.parseURLEncodedText(lines[lines.length-1]);
1421             if (params)
1422                 this.insertParameters(parentNode, params);
1423         }
1424 
1425         if (NetUtils.isMultiPartRequest(file, context))
1426         {
1427             var data = this.parseMultiPartText(file, context);
1428             if (data)
1429                 this.insertParts(parentNode, data);
1430         }
1431 
1432         var contentType = NetUtils.findHeader(file.requestHeaders, "content-type");
1433 
1434         if (Firebug.JSONViewerModel.isJSON(contentType, text))
1435             this.insertJSON(parentNode, file, context);
1436 
1437         if (Firebug.XMLViewerModel.isXML(contentType))
1438             this.insertXML(parentNode, file, context);
1439 
1440         if (Firebug.SVGViewerModel.isSVG(contentType))
1441             this.insertSVG(parentNode, file, context);
1442 
1443         if (Firebug.FontViewerModel.isFont(contentType, file.href, text))
1444             this.insertFont(parentNode, file, context);
1445 
1446         var postText = NetUtils.getPostText(file, context);
1447 
1448         // Make sure headers are not displayed in the 'source' section.
1449         postText = Http.removeHeadersFromPostText(file.request, postText);
1450         postText = NetUtils.formatPostText(postText);
1451         if (postText)
1452             this.insertSource(parentNode, postText);
1453     },
1454 
1455     insertParameters: function(parentNode, params)
1456     {
1457         if (!params || !params.length)
1458             return;
1459 
1460         var paramTable = this.paramsTable.append(null, parentNode);
1461         var row = paramTable.getElementsByClassName("netInfoPostParamsTitle").item(0);
1462 
1463         Firebug.NetMonitor.NetInfoBody.headerDataTag.insertRows({headers: params}, row);
1464     },
1465 
1466     insertParts: function(parentNode, data)
1467     {
1468         if (!data.params || !data.params.length)
1469             return;
1470 
1471         var partsTable = this.partsTable.append(null, parentNode);
1472         var row = partsTable.getElementsByClassName("netInfoPostPartsTitle").item(0);
1473 
1474         Firebug.NetMonitor.NetInfoBody.headerDataTag.insertRows({headers: data.params}, row);
1475     },
1476 
1477     insertJSON: function(parentNode, file, context)
1478     {
1479         var text = NetUtils.getPostText(file, context);
1480         var data = Json.parseJSONString(text, "http://" + file.request.originalURI.host);
1481         if (!data)
1482             return;
1483 
1484         var jsonTable = this.jsonTable.append(null, parentNode);
1485         var jsonBody = jsonTable.getElementsByClassName("netInfoPostJSONBody").item(0);
1486 
1487         if (!this.toggles)
1488             this.toggles = new ToggleBranch.ToggleBranch();
1489 
1490         Firebug.DOMPanel.DirTable.tag.replace(
1491             {object: data, toggles: this.toggles}, jsonBody);
1492     },
1493 
1494     insertXML: function(parentNode, file, context)
1495     {
1496         var text = NetUtils.getPostText(file, context);
1497 
1498         var jsonTable = this.xmlTable.append(null, parentNode);
1499         var jsonBody = jsonTable.getElementsByClassName("netInfoPostXMLBody").item(0);
1500 
1501         Firebug.XMLViewerModel.insertXML(jsonBody, text);
1502     },
1503 
1504     insertSVG: function(parentNode, file, context)
1505     {
1506         var text = NetUtils.getPostText(file, context);
1507 
1508         var jsonTable = this.svgTable.append(null, parentNode);
1509         var jsonBody = jsonTable.getElementsByClassName("netInfoPostSVGBody").item(0);
1510 
1511         Firebug.SVGViewerModel.insertSVG(jsonBody, text);
1512     },
1513 
1514     insertFont: function(parentNode, file, context)
1515     {
1516         var text = NetUtils.getPostText(file, context);
1517 
1518         var fontTable = this.fontTable.append(null, parentNode);
1519         var fontBody = fontTable.getElementsByClassName("netInfoPostFontBody").item(0);
1520 
1521         Firebug.FontViewerModel.insertFont(fontBody, text);
1522     },
1523 
1524     insertSource: function(parentNode, text)
1525     {
1526         var sourceTable = this.sourceTable.append(null, parentNode);
1527         var row = sourceTable.getElementsByClassName("netInfoPostSourceTitle").item(0);
1528 
1529         var param = {value: text};
1530         this.sourceBodyTag.insertRows({param: param}, row);
1531     },
1532 
1533     parseMultiPartText: function(file, context)
1534     {
1535         var text = NetUtils.getPostText(file, context);
1536         if (text == undefined)
1537             return null;
1538 
1539         FBTrace.sysout("net.parseMultiPartText; boundary: ", text);
1540 
1541         var boundary = text.match(/\s*boundary=\s*(.*)/)[1];
1542 
1543         var divider = "\r\n\r\n";
1544         var bodyStart = text.indexOf(divider);
1545         var body = text.substr(bodyStart + divider.length);
1546 
1547         var postData = {};
1548         postData.mimeType = "multipart/form-data";
1549         postData.params = [];
1550 
1551         var parts = body.split("--" + boundary);
1552         for (var i=0; i<parts.length; i++)
1553         {
1554             var part = parts[i].split(divider);
1555             if (part.length != 2)
1556                 continue;
1557 
1558             var m = part[0].match(/\s*name=\"(.*)\"(;|$)/);
1559             postData.params.push({
1560                 name: (m && m.length > 1) ? m[1] : "",
1561                 value: Str.trim(part[1])
1562             })
1563         }
1564 
1565         return postData;
1566     }
1567 });
1568 
1569 // ********************************************************************************************* //
1570 
1571 /**
1572  * @domplate Used within the Net panel to display raw source of request and response headers
1573  * as well as pretty-formatted summary of these headers.
1574  */
1575 Firebug.NetMonitor.NetInfoHeaders = domplate(Firebug.Rep, new Firebug.Listener(),
1576 {
1577     tag:
1578         DIV({"class": "netInfoHeadersTable", "role": "tabpanel"},
1579             DIV({"class": "netInfoHeadersGroup netInfoResponseHeadersTitle collapsed"},
1580                 SPAN(Locale.$STR("ResponseHeaders")),
1581                 SPAN({"class": "netHeadersViewSource response collapsed", onclick: "$onViewSource",
1582                     _sourceDisplayed: false, _rowName: "ResponseHeaders"},
1583                     Locale.$STR("net.headers.view source")
1584                 )
1585             ),
1586             TABLE({cellpadding: 0, cellspacing: 0},
1587                 TBODY({"class": "netInfoResponseHeadersBody", "role": "list",
1588                     "aria-label": Locale.$STR("ResponseHeaders")})
1589             ),
1590             DIV({"class": "netInfoHeadersGroup netInfoRequestHeadersTitle collapsed"},
1591                 SPAN(Locale.$STR("RequestHeaders")),
1592                 SPAN({"class": "netHeadersViewSource request collapsed", onclick: "$onViewSource",
1593                     _sourceDisplayed: false, _rowName: "RequestHeaders"},
1594                     Locale.$STR("net.headers.view source")
1595                 )
1596             ),
1597             TABLE({cellpadding: 0, cellspacing: 0},
1598                 TBODY({"class": "netInfoRequestHeadersBody", "role": "list",
1599                     "aria-label": Locale.$STR("RequestHeaders")})
1600             ),
1601             DIV({"class": "netInfoHeadersGroup netInfoCachedResponseHeadersTitle collapsed"},
1602                 SPAN(Locale.$STR("CachedResponseHeaders"))
1603             ),
1604             TABLE({cellpadding: 0, cellspacing: 0},
1605                 TBODY({"class": "netInfoCachedResponseHeadersBody", "role": "list",
1606                     "aria-label": Locale.$STR("CachedResponseHeaders")})
1607             ),
1608             DIV({"class": "netInfoHeadersGroup netInfoPostRequestHeadersTitle collapsed"},
1609                 SPAN(Locale.$STR("PostRequestHeaders"))
1610             ),
1611             TABLE({cellpadding: 0, cellspacing: 0},
1612                 TBODY({"class": "netInfoPostRequestHeadersBody", "role": "list",
1613                     "aria-label": Locale.$STR("PostRequestHeaders")})
1614             )
1615         ),
1616 
1617     sourceTag:
1618         TR({"role": "presentation"},
1619             TD({colspan: 2, "role": "presentation"},
1620                 PRE({"class": "source"})
1621             )
1622         ),
1623 
1624     onViewSource: function(event)
1625     {
1626         var target = event.target;
1627         var requestHeaders = (target.rowName == "RequestHeaders");
1628 
1629         var netInfoBox = Dom.getAncestorByClass(target, "netInfoBody");
1630         var file = netInfoBox.repObject;
1631 
1632         if (target.sourceDisplayed)
1633         {
1634             var headers = requestHeaders ? file.requestHeaders : file.responseHeaders;
1635             this.insertHeaderRows(netInfoBox, headers, target.rowName);
1636             target.textContent = Locale.$STR("net.headers.view source");
1637         }
1638         else
1639         {
1640             var source = requestHeaders ? file.requestHeadersText : file.responseHeadersText;
1641             this.insertSource(netInfoBox, Str.escapeForTextNode(source), target.rowName);
1642             target.textContent = Locale.$STR("net.headers.pretty print");
1643         }
1644 
1645         target.sourceDisplayed = !target.sourceDisplayed;
1646 
1647         Events.cancelEvent(event);
1648     },
1649 
1650     insertSource: function(netInfoBox, source, rowName)
1651     {
1652         // This breaks copy to clipboard.
1653         //if (source)
1654         //    source = source.replace(/\r\n/gm, "<span style='color:lightgray'>\\r\\n</span>\r\n");
1655 
1656         var tbody = netInfoBox.getElementsByClassName("netInfo" + rowName + "Body").item(0);
1657         var node = this.sourceTag.replace({}, tbody);
1658         var sourceNode = node.getElementsByClassName("source").item(0);
1659         sourceNode.innerHTML = source;
1660     },
1661 
1662     insertHeaderRows: function(netInfoBox, headers, rowName)
1663     {
1664         var headersTable = netInfoBox.getElementsByClassName("netInfoHeadersTable").item(0);
1665         var tbody = headersTable.getElementsByClassName("netInfo" + rowName + "Body").item(0);
1666 
1667         Dom.clearNode(tbody);
1668 
1669         if (headers && headers.length)
1670         {
1671             headers.sort(function(a, b)
1672             {
1673                 return a.name > b.name ? 1 : -1;
1674             });
1675 
1676             Firebug.NetMonitor.NetInfoBody.headerDataTag.insertRows({headers: headers}, tbody);
1677 
1678             var titleRow = Dom.getChildByClass(headersTable, "netInfo" + rowName + "Title");
1679             Css.removeClass(titleRow, "collapsed");
1680         }
1681     },
1682 
1683     init: function(parent)
1684     {
1685         var rootNode = this.tag.append({}, parent);
1686 
1687         var netInfoBox = Dom.getAncestorByClass(parent, "netInfoBody");
1688         var file = netInfoBox.repObject;
1689 
1690         var viewSource;
1691 
1692         viewSource = rootNode.getElementsByClassName("netHeadersViewSource request").item(0);
1693         if (file.requestHeadersText)
1694             Css.removeClass(viewSource, "collapsed");
1695 
1696         viewSource = rootNode.getElementsByClassName("netHeadersViewSource response").item(0);
1697         if (file.responseHeadersText)
1698             Css.removeClass(viewSource, "collapsed");
1699     },
1700 
1701     renderHeaders: function(parent, headers, rowName)
1702     {
1703         if (!parent.firstChild)
1704             this.init(parent);
1705 
1706         this.insertHeaderRows(parent, headers, rowName);
1707     }
1708 });
1709 
1710 // ********************************************************************************************* //
1711 
1712 /**
1713  * @domplate Represents a template for popup tip that displays detailed timing info about
1714  * a network request.
1715  */
1716 Firebug.NetMonitor.TimeInfoTip = domplate(Firebug.Rep,
1717 {
1718     tableTag:
1719         TABLE({"class": "timeInfoTip", "id": "fbNetTimeInfoTip"},
1720             TBODY()
1721         ),
1722 
1723     timingsTag:
1724         FOR("time", "$timings",
1725             TR({"class": "timeInfoTipRow", $collapsed: "$time|hideBar"},
1726                 TD({"class": "$time|getBarClass timeInfoTipBar",
1727                     $loaded: "$time.loaded",
1728                     $fromCache: "$time.fromCache",
1729                 }),
1730                 TD({"class": "timeInfoTipCell startTime"},
1731                     "$time.start|formatStartTime"
1732                 ),
1733                 TD({"class": "timeInfoTipCell elapsedTime"},
1734                     "$time.elapsed|formatTime"
1735                 ),
1736                 TD("$time|getLabel")
1737             )
1738         ),
1739 
1740     startTimeTag:
1741         TR(
1742             TD(),
1743             TD("$startTime.time|formatStartTime"),
1744             TD({"class": "timeInfoTipStartLabel", "colspan": 2},
1745                 "$startTime|getLabel"
1746             )
1747         ),
1748 
1749     separatorTag:
1750         TR(
1751             TD({"class": "timeInfoTipSeparator", "colspan": 4, "height": "10px"},
1752                 SPAN("$label")
1753             )
1754         ),
1755 
1756     eventsTag:
1757         FOR("event", "$events",
1758             TR({"class": "timeInfoTipEventRow"},
1759                 TD({"class": "timeInfoTipBar", align: "center"},
1760                     DIV({"class": "$event|getTimeStampClass timeInfoTipEventBar"})
1761                 ),
1762                 TD("$event.start|formatStartTime"),
1763                 TD({"class": "timeInfotTipEventName", "colspan": 2},
1764                     "$event|getTimeStampLabel"
1765                 )
1766             )
1767         ),
1768 
1769     hideBar: function(obj)
1770     {
1771         return !obj.elapsed && obj.bar == "Blocking";
1772     },
1773 
1774     getBarClass: function(obj)
1775     {
1776         return "net" + obj.bar + "Bar";
1777     },
1778 
1779     getTimeStampClass: function(obj)
1780     {
1781         return obj.classes;
1782     },
1783 
1784     formatTime: function(time)
1785     {
1786         return Str.formatTime(time);
1787     },
1788 
1789     formatStartTime: function(time)
1790     {
1791         var label = Str.formatTime(time);
1792         if (!time)
1793             return label;
1794 
1795         return (time > 0 ? "+" : "") + label;
1796     },
1797 
1798     getLabel: function(obj)
1799     {
1800         return Locale.$STR("requestinfo." + obj.bar);
1801     },
1802 
1803     getTimeStampLabel: function(obj)
1804     {
1805         return obj.name;
1806     },
1807 
1808     render: function(context, file, parentNode)
1809     {
1810         var infoTip = Firebug.NetMonitor.TimeInfoTip.tableTag.replace({}, parentNode);
1811 
1812         var elapsed = file.loaded ? file.endTime - file.startTime :
1813             file.phase.phaseEndTime - file.startTime;
1814         var blockingEnd = NetUtils.getBlockingEndTime(file);
1815 
1816         //Helper log for debugging timing problems.
1817         //NetUtils.traceRequestTiming("net.timeInfoTip.render;", file);
1818 
1819         var startTime = 0;
1820 
1821         var timings = [];
1822         timings.push({bar: "Blocking",
1823             elapsed: blockingEnd - file.startTime,
1824             start: startTime});
1825 
1826         timings.push({bar: "Resolving",
1827             elapsed: file.connectingTime - file.resolvingTime,
1828             start: startTime += timings[0].elapsed});
1829 
1830         // Connecting time is measured till the sending time in order to include
1831         // also SSL negotiation.
1832         // xxxHonza: time between "connected" and "sending" is SSL negotiation?
1833         timings.push({bar: "Connecting",
1834             elapsed: file.connectStarted ? file.sendingTime - file.connectingTime : 0,
1835             start: startTime += timings[1].elapsed});
1836 
1837         // In Fx3.6 the STATUS_SENDING_TO is always fired (nsIHttpActivityDistributor)
1838         // In Fx3.5 the STATUS_SENDING_TO (nsIWebProgressListener) doesn't have to come
1839         // This workaround is for 3.5
1840         var sendElapsed = file.sendStarted ? file.waitingForTime - file.sendingTime : 0;
1841         var sendStarted = timings[0].elapsed + timings[1].elapsed + timings[2].elapsed;
1842 
1843         timings.push({bar: "Sending",
1844             elapsed: sendElapsed,
1845             start: file.sendStarted ? file.sendingTime - file.startTime : sendStarted});
1846 
1847         timings.push({bar: "Waiting",
1848             elapsed: file.respondedTime - file.waitingForTime,
1849             start: file.waitingForTime - file.startTime});
1850 
1851         timings.push({bar: "Receiving",
1852             elapsed: file.endTime - file.respondedTime,
1853             start: file.respondedTime - file.startTime,
1854             loaded: file.loaded, fromCache: file.fromCache});
1855 
1856         var events = [];
1857         var timeStamps = file.phase.timeStamps;
1858         for (var i=0; i<timeStamps.length; i++)
1859         {
1860             var timeStamp = timeStamps[i];
1861             events.push({
1862                 name: timeStamp.label,
1863                 classes: timeStamp.classes,
1864                 start: timeStamp.time - file.startTime
1865             })
1866         }
1867 
1868         events.sort(function(a, b) {
1869             return a.start < b.start ? -1 : 1;
1870         })
1871 
1872         var phases = context.netProgress.phases;
1873 
1874         if (FBTrace.DBG_ERROR && phases.length == 0)
1875             FBTrace.sysout("net.render; ERROR no phases");
1876 
1877         // Insert start request time. It's computed since the beginning (page load start time)
1878         // i.e. from the first phase start.
1879         var firstPhaseStartTime = (phases.length > 0) ? phases[0].startTime : file.startTime;
1880 
1881         var startTime = {};
1882         startTime.time = file.startTime - firstPhaseStartTime;
1883         startTime.bar = "started.label";
1884         this.startTimeTag.insertRows({startTime: startTime}, infoTip.firstChild);
1885 
1886         // Insert separator.
1887         this.separatorTag.insertRows({label: Locale.$STR("requestinfo.phases.label")},
1888             infoTip.firstChild);
1889 
1890         // Insert request timing info.
1891         this.timingsTag.insertRows({timings: timings}, infoTip.firstChild);
1892 
1893         // Insert events timing info.
1894         if (events.length)
1895         {
1896             // Insert separator.
1897             this.separatorTag.insertRows({label: Locale.$STR("requestinfo.timings.label")},
1898                 infoTip.firstChild);
1899 
1900             this.eventsTag.insertRows({events: events}, infoTip.firstChild);
1901         }
1902 
1903         return true;
1904     }
1905 });
1906 
1907 // ********************************************************************************************* //
1908 
1909 /**
1910  * @domplate Represents a template for a pupup tip with detailed size info.
1911  */
1912 Firebug.NetMonitor.SizeInfoTip = domplate(Firebug.Rep,
1913 {
1914     tag:
1915         TABLE({"class": "sizeInfoTip", "id": "fbNetSizeInfoTip", role:"presentation"},
1916             TBODY(
1917                 FOR("size", "$sizeInfo",
1918                     TAG("$size|getRowTag", {size: "$size"})
1919                 )
1920             )
1921         ),
1922 
1923     sizeTag:
1924         TR({"class": "sizeInfoRow", $collapsed: "$size|hideRow"},
1925             TD({"class": "sizeInfoLabelCol"}, "$size.label"),
1926             TD({"class": "sizeInfoSizeCol"}, "$size|formatSize"),
1927             TD({"class": "sizeInfoDetailCol"}, "$size|formatNumber")
1928         ),
1929 
1930     separatorTag:
1931         TR(
1932             TD({"colspan": 3, "height": "7px"})
1933         ),
1934 
1935     descTag:
1936         TR(
1937             TD({"colspan": 3, "class": "sizeInfoDescCol"}, "$size.label")
1938         ),
1939 
1940     getRowTag: function(size)
1941     {
1942         if (size.size == -2)
1943             return this.descTag;
1944 
1945         return (size.label == "-") ? this.separatorTag : this.sizeTag;
1946     },
1947 
1948     hideRow: function(size)
1949     {
1950         return size.size < 0;
1951     },
1952 
1953     formatSize: function(size)
1954     {
1955         return Str.formatSize(size.size);
1956     },
1957 
1958     formatNumber: function(size)
1959     {
1960         return size.size && size.size >= 1024 ? "(" + size.size.toLocaleString() + " B)" : "";
1961     },
1962 
1963     render: function(file, parentNode)
1964     {
1965         var postText = NetUtils.getPostText(file, Firebug.currentContext, true);
1966         postText = postText ? postText : "";
1967 
1968         var sizeInfo = [];
1969         sizeInfo.push({label: Locale.$STR("net.sizeinfo.Response Body"), size: file.size});
1970         sizeInfo.push({label: Locale.$STR("net.sizeinfo.Post Body"), size: postText.length});
1971 
1972         if (file.requestHeadersText)
1973         {
1974             var responseHeaders = file.responseHeadersText ? file.responseHeadersText : 0;
1975             sizeInfo.push({label: "-", size: 0});
1976             sizeInfo.push({label: Locale.$STR("net.sizeinfo.Total Received") + "*",
1977                 size: (responseHeaders.length ? responseHeaders.length : 0)  + file.size});
1978             sizeInfo.push({label: Locale.$STR("net.sizeinfo.Total Sent") + "*",
1979                 size: file.requestHeadersText.length + postText.length});
1980             sizeInfo.push({label: " ", size: -2});
1981             sizeInfo.push({label: "* " + Locale.$STR("net.sizeinfo.Including HTTP Headers"),
1982                 size: -2});
1983         }
1984 
1985         this.tag.replace({sizeInfo: sizeInfo}, parentNode);
1986     },
1987 });
1988 
1989 // ********************************************************************************************* //
1990 
1991 Firebug.NetMonitor.NetLimit = domplate(Firebug.Rep,
1992 {
1993     collapsed: true,
1994 
1995     tableTag:
1996         DIV(
1997             TABLE({width: "100%", cellpadding: 0, cellspacing: 0},
1998                 TBODY()
1999             )
2000         ),
2001 
2002     limitTag:
2003         TR({"class": "netRow netLimitRow", $collapsed: "$isCollapsed"},
2004             TD({"class": "netCol netLimitCol", colspan: 8},
2005                 TABLE({cellpadding: 0, cellspacing: 0},
2006                     TBODY(
2007                         TR(
2008                             TD(
2009                                 SPAN({"class": "netLimitLabel"},
2010                                     Locale.$STRP("plural.Limit_Exceeded2", [0])
2011                                 )
2012                             ),
2013                             TD({style: "width:100%"}),
2014                             TD(
2015                                 BUTTON({"class": "netLimitButton", title: "$limitPrefsTitle",
2016                                     onclick: "$onPreferences"},
2017                                   Locale.$STR("LimitPrefs")
2018                                 )
2019                             ),
2020                             TD(" ")
2021                         )
2022                     )
2023                 )
2024             )
2025         ),
2026 
2027     isCollapsed: function()
2028     {
2029         return this.collapsed;
2030     },
2031 
2032     onPreferences: function(event)
2033     {
2034         Win.openNewTab("about:config");
2035     },
2036 
2037     updateCounter: function(row)
2038     {
2039         Css.removeClass(row, "collapsed");
2040 
2041         // Update info within the limit row.
2042         var limitLabel = row.getElementsByClassName("netLimitLabel").item(0);
2043         limitLabel.firstChild.nodeValue = Locale.$STRP("plural.Limit_Exceeded2",
2044             [row.limitInfo.totalCount]);
2045     },
2046 
2047     createTable: function(parent, limitInfo)
2048     {
2049         var table = this.tableTag.replace({}, parent);
2050         var row = this.createRow(table.firstChild.firstChild, limitInfo);
2051         return [table, row];
2052     },
2053 
2054     createRow: function(parent, limitInfo)
2055     {
2056         var row = this.limitTag.insertRows(limitInfo, parent, this)[0];
2057         row.limitInfo = limitInfo;
2058         return row;
2059     },
2060 });
2061 
2062 // ********************************************************************************************* //
2063 
2064 Firebug.NetMonitor.ResponseSizeLimit = domplate(Firebug.Rep,
2065 {
2066     tag:
2067         DIV({"class": "netInfoResponseSizeLimit"},
2068             SPAN("$object.beforeLink"),
2069             A({"class": "objectLink", onclick: "$onClickLink"},
2070                 "$object.linkText"
2071             ),
2072             SPAN("$object.afterLink")
2073         ),
2074 
2075     reLink: /^(.*)<a>(.*)<\/a>(.*$)/,
2076     append: function(obj, parent)
2077     {
2078         var m = obj.text.match(this.reLink);
2079         return this.tag.append({onClickLink: obj.onClickLink,
2080             object: {
2081             beforeLink: m[1],
2082             linkText: m[2],
2083             afterLink: m[3],
2084         }}, parent, this);
2085     }
2086 });
2087 
2088 // ********************************************************************************************* //
2089 // Registration
2090 
2091 Firebug.registerRep(Firebug.NetMonitor.NetRequestTable);
2092 
2093 return Firebug.NetMonitor.NetRequestTable;
2094 
2095 // ********************************************************************************************* //
2096 }});
2097