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/xpcom",
  9     "firebug/lib/locale",
 10     "firebug/lib/events",
 11     "firebug/lib/options",
 12     "firebug/lib/url",
 13     "firebug/js/sourceLink",
 14     "firebug/lib/http",
 15     "firebug/lib/css",
 16     "firebug/lib/dom",
 17     "firebug/chrome/window",
 18     "firebug/lib/search",
 19     "firebug/lib/string",
 20     "firebug/lib/array",
 21     "firebug/lib/system",
 22     "firebug/chrome/menu",
 23     "firebug/net/netUtils",
 24     "firebug/net/netProgress",
 25     "firebug/css/cssReps",
 26     "firebug/js/breakpoint",
 27     "firebug/net/xmlViewer",
 28     "firebug/net/svgViewer",
 29     "firebug/net/jsonViewer",
 30     "firebug/net/fontViewer",
 31     "firebug/chrome/infotip",
 32     "firebug/css/cssPanel",
 33     "firebug/chrome/searchBox",
 34     "firebug/console/errors",
 35     "firebug/net/netMonitor",
 36     "firebug/net/netReps"
 37 ],
 38 function(Obj, Firebug, Firefox, Domplate, Xpcom, Locale,
 39     Events, Options, Url, SourceLink, Http, Css, Dom, Win, Search, Str,
 40     Arr, System, Menu, NetUtils, NetProgress, CSSInfoTip) {
 41 
 42 with (Domplate) {
 43 
 44 // ********************************************************************************************* //
 45 // Constants
 46 
 47 const Cc = Components.classes;
 48 const Ci = Components.interfaces;
 49 const Cr = Components.results;
 50 
 51 var layoutInterval = 300;
 52 var panelName = "net";
 53 var NetRequestEntry = Firebug.NetMonitor.NetRequestEntry;
 54 
 55 // ********************************************************************************************* //
 56 
 57 /**
 58  * @panel Represents a Firebug panel that displayes info about HTTP activity associated with
 59  * the current page. This class is derived from <code>Firebug.ActivablePanel</code> in order
 60  * to support activation (enable/disable). This allows to avoid (performance) expensive
 61  * features if the functionality is not necessary for the user.
 62  */
 63 function NetPanel() {}
 64 NetPanel.prototype = Obj.extend(Firebug.ActivablePanel,
 65 /** lends NetPanel */
 66 {
 67     name: panelName,
 68     searchable: true,
 69     editable: true,
 70     breakable: true,
 71     enableA11y: true,
 72     order: 60,
 73 
 74     initialize: function(context, doc)
 75     {
 76         if (FBTrace.DBG_NET)
 77             FBTrace.sysout("net.NetPanel.initialize; " + context.getName());
 78 
 79         this.queue = [];
 80         this.onContextMenu = Obj.bind(this.onContextMenu, this);
 81 
 82         Firebug.ActivablePanel.initialize.apply(this, arguments);
 83     },
 84 
 85     destroy: function(state)
 86     {
 87         Firebug.ActivablePanel.destroy.apply(this, arguments);
 88     },
 89 
 90     initializeNode : function()
 91     {
 92         Events.addEventListener(this.panelNode, "contextmenu", this.onContextMenu, false);
 93 
 94         this.onResizer = Obj.bind(this.onResize, this);
 95         this.resizeEventTarget = Firebug.chrome.$('fbContentBox');
 96         Events.addEventListener(this.resizeEventTarget, "resize", this.onResizer, true);
 97 
 98         Firebug.ActivablePanel.initializeNode.apply(this, arguments);
 99     },
100 
101     destroyNode : function()
102     {
103         Events.removeEventListener(this.panelNode, "contextmenu", this.onContextMenu, false);
104         Events.removeEventListener(this.resizeEventTarget, "resize", this.onResizer, true);
105 
106         Firebug.ActivablePanel.destroyNode.apply(this, arguments);
107     },
108 
109     loadPersistedContent: function(state)
110     {
111         this.initLayout();
112 
113         var tbody = this.table.querySelector(".netTableBody");
114 
115         // Move all net-rows from the persistedState to this panel.
116         var prevTableBody = state.panelNode.getElementsByClassName("netTableBody").item(0);
117         if (!prevTableBody)
118             return;
119 
120         var files = [];
121 
122         // Iterate persisted content - table rows. These rows can represent various things
123         // 1) netPageRow - already persisted group
124         // 2) netRow - request entries from the previous session (page load)
125         while (prevTableBody.firstChild)
126         {
127             var row = prevTableBody.firstChild;
128 
129             // Collect all entries that belongs to the current page load (not history)
130             if (Css.hasClass(row, "netRow") &&
131                 Css.hasClass(row, "hasHeaders") &&
132                 !Css.hasClass(row, "history"))
133             {
134                 row.repObject.history = true;
135                 files.push({
136                     file: row.repObject,
137                     offset: 0 + "%",
138                     width: 0 + "%",
139                     elapsed:  -1
140                 });
141             }
142 
143             if (Css.hasClass(row, "netPageRow"))
144             {
145                 Css.removeClass(row, "opened");
146 
147                 // Insert the old page-load-history entry just before the summary-row,
148                 // but after the limit row.
149                 tbody.insertBefore(row, this.summaryRow);
150             }
151             else
152             {
153                 prevTableBody.removeChild(row);
154             }
155         }
156 
157         // New page-load-history entry is inserted just before summary row
158         // (at the end of page-load-history entry list)
159         var lastRow = this.summaryRow.previousSibling;
160         if (files.length)
161         {
162             var pageRow = Firebug.NetMonitor.NetPage.pageTag.insertRows({page: state}, lastRow)[0];
163             pageRow.files = files;
164 
165             lastRow = this.summaryRow.previousSibling;
166         }
167 
168         // Insert a separator tag at the end of page-load-history entry list.
169         if (this.table.getElementsByClassName("netPageRow").item(0))
170             Firebug.NetMonitor.NetPage.separatorTag.insertRows({}, lastRow);
171 
172         Dom.scrollToBottom(this.panelNode);
173     },
174 
175     savePersistedContent: function(state)
176     {
177         Firebug.ActivablePanel.savePersistedContent.apply(this, arguments);
178 
179         state.pageTitle = NetUtils.getPageTitle(this.context);
180     },
181 
182     show: function(state)
183     {
184         if (FBTrace.DBG_NET)
185             FBTrace.sysout("net.netPanel.show; " + this.context.getName(), state);
186 
187         var enabled = Firebug.NetMonitor.isAlwaysEnabled();
188         this.showToolbarButtons("fbNetButtons", enabled);
189 
190         if (enabled)
191             Firebug.chrome.setGlobalAttribute("cmd_firebug_togglePersistNet", "checked", this.persistContent);
192         else
193             this.table = null;
194 
195         if (!enabled)
196             return;
197 
198         if (!this.filterCategory)
199             this.setFilter(Firebug.netFilterCategory);
200 
201         this.layout();
202 
203         if (!this.layoutInterval)
204             this.layoutInterval = setInterval(Obj.bindFixed(this.updateLayout, this), layoutInterval);
205 
206         if (this.wasScrolledToBottom)
207             Dom.scrollToBottom(this.panelNode);
208     },
209 
210     hide: function()
211     {
212         if (FBTrace.DBG_NET)
213             FBTrace.sysout("net.netPanel.hide; " + this.context.getName());
214 
215         delete this.infoTipURL;  // clear the state that is tracking the infotip so it is reset after next show()
216         this.wasScrolledToBottom = Dom.isScrolledToBottom(this.panelNode);
217 
218         clearInterval(this.layoutInterval);
219         delete this.layoutInterval;
220     },
221 
222     updateOption: function(name, value)
223     {
224         if (name == "netFilterCategory")
225         {
226             Firebug.NetMonitor.syncFilterButtons(Firebug.chrome);
227             Firebug.connection.eachContext(function syncFilters(context)
228             {
229                 Firebug.NetMonitor.onToggleFilter(context, value);
230             });
231         }
232         else if (name == "netShowBFCacheResponses")
233         {
234             this.updateBFCacheResponses();
235         }
236     },
237 
238     updateBFCacheResponses: function()
239     {
240         if (this.table)
241         {
242             if (Firebug.netShowBFCacheResponses)
243                 Css.setClass(this.table, "showBFCacheResponses");
244             else
245                 Css.removeClass(this.table, "showBFCacheResponses");
246 
247             // Recalculate the summary information since some requests doesn't have to
248             // be displayed now.
249             this.updateSummaries(NetUtils.now(), true);
250         }
251     },
252 
253     updateSelection: function(object)
254     {
255         if (!object)
256             return;
257 
258         var netProgress = this.context.netProgress;
259         var file = netProgress.getRequestFile(object.request);
260         if (!file)
261         {
262             for (var i=0; i<netProgress.requests.length; i++) {
263                 if (Http.safeGetRequestName(netProgress.requests[i]) == object.href) {
264                    file = netProgress.files[i];
265                    break;
266                 }
267             }
268         }
269 
270         if (file)
271         {
272             Dom.scrollIntoCenterView(file.row);
273             if (!Css.hasClass(file.row, "opened"))
274                 NetRequestEntry.toggleHeadersRow(file.row);
275         }
276     },
277 
278     getPopupObject: function(target)
279     {
280         var header = Dom.getAncestorByClass(target, "netHeaderRow");
281         if (header)
282             return Firebug.NetMonitor.NetRequestTable;
283 
284         return Firebug.ActivablePanel.getPopupObject.apply(this, arguments);
285     },
286 
287     supportsObject: function(object, type)
288     {
289         return ((object instanceof SourceLink.SourceLink && object.type == "net") ? 2 : 0);
290     },
291 
292     getOptionsMenuItems: function()
293     {
294         return [
295             this.disableCacheOption(),
296             "-",
297             Menu.optionMenu("net.option.Show_Paint_Events", "netShowPaintEvents",
298                 "net.option.tip.Show_Paint_Events"),
299             Menu.optionMenu("net.option.Show_BFCache_Responses", "netShowBFCacheResponses",
300                 "net.option.tip.Show_BFCache_Responses")
301         ];
302     },
303 
304     disableCacheOption: function()
305     {
306         var BrowserCache = Firebug.NetMonitor.BrowserCache;
307         var disabled = !BrowserCache.isEnabled();
308         return {
309             label: "net.option.Disable_Browser_Cache",
310             type: "checkbox",
311             checked: disabled,
312             tooltiptext: "net.option.tip.Disable_Browser_Cache",
313             command: function()
314             {
315                 BrowserCache.toggle(!this.hasAttribute("checked"));
316             }
317         };
318     },
319 
320     getContextMenuItems: function(nada, target)
321     {
322         var items = [];
323 
324         var file = Firebug.getRepObject(target);
325         if (!file || !(file instanceof Firebug.NetFile))
326             return items;
327 
328         var object = Firebug.getObjectByURL(this.context, file.href);
329         var isPost = NetUtils.isURLEncodedRequest(file, this.context);
330         var params = Url.parseURLParams(file.href);
331 
332         items.push(
333             {
334                 label: "CopyLocation",
335                 tooltiptext: "clipboard.tip.Copy_Location",
336                 command: Obj.bindFixed(System.copyToClipboard, System, file.href)
337             }
338         );
339 
340         if (params.length > 0)
341         {
342             items.push(
343                 {
344                     id: "fbCopyUrlParameters",
345                     label: "CopyURLParameters",
346                     tooltiptext: "net.tip.Copy_URL_Parameters",
347                     command: Obj.bindFixed(this.copyURLParams, this, file)
348                 }
349             );
350         }
351 
352         if (isPost)
353         {
354             items.push(
355                 {
356                     label: "CopyLocationParameters",
357                     tooltiptext: "net.tip.Copy_Location_Parameters",
358                     command: Obj.bindFixed(this.copyParams, this, file)
359                 },
360                 {
361                     id: "fbCopyPOSTParameters",
362                     label: "CopyPOSTParameters",
363                     tooltiptext: "net.tip.Copy_POST_Parameters",
364                     command: Obj.bindFixed(this.copyPOSTParams, this, file)
365                 }
366             );
367         }
368 
369         items.push(
370             {
371                 label: "CopyRequestHeaders",
372                 tooltiptext: "net.tip.Copy_Request_Headers",
373                 command: Obj.bindFixed(this.copyRequestHeaders, this, file)
374             },
375             {
376                 label: "CopyResponseHeaders",
377                 tooltiptext: "net.tip.Copy_Response_Headers",
378                 command: Obj.bindFixed(this.copyResponseHeaders, this, file)
379             }
380         );
381 
382         if (NetUtils.textFileCategories.hasOwnProperty(file.category))
383         {
384             items.push(
385                 {
386                     label: "CopyResponse",
387                     tooltiptext: "net.tip.Copy_Response",
388                     command: Obj.bindFixed(this.copyResponse, this, file)
389                 }
390             );
391         }
392 
393         items.push(
394             "-",
395             {
396                 label: "OpenInTab",
397                 tooltiptext: "firebug.tip.Open_In_Tab",
398                 command: Obj.bindFixed(this.openRequestInTab, this, file)
399             }
400         );
401 
402         if (NetUtils.textFileCategories.hasOwnProperty(file.category))
403         {
404             items.push(
405                 {
406                     label: "Open_Response_In_New_Tab",
407                     tooltiptext: "net.tip.Open_Response_In_New_Tab",
408                     command: Obj.bindFixed(NetUtils.openResponseInTab, this, file)
409                 }
410             );
411         }
412 
413         if (!file.loaded)
414         {
415             items.push(
416                 "-",
417                 {
418                     label: "StopLoading",
419                     tooltiptext: "net.tip.Stop_Loading",
420                     command: Obj.bindFixed(this.stopLoading, this, file)
421                 }
422             );
423         }
424 
425         if (object)
426         {
427             var subItems = Firebug.chrome.getInspectMenuItems(object);
428             if (subItems.length)
429             {
430                 items.push("-");
431                 items.push.apply(items, subItems);
432             }
433         }
434 
435         if (file.isXHR)
436         {
437             var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
438 
439             items.push(
440                 "-",
441                 {
442                     label: "net.label.Break_On_XHR",
443                     tooltiptext: "net.tip.Break_On_XHR",
444                     type: "checkbox",
445                     checked: !!bp,
446                     command: Obj.bindFixed(this.breakOnRequest, this, file)
447                 }
448             );
449 
450             if (bp)
451             {
452                 items.push(
453                     {
454                         label: "EditBreakpointCondition",
455                         tooltiptext: "breakpoints.tip.Edit_Breakpoint_Condition",
456                         command: Obj.bindFixed(this.editBreakpointCondition, this, file)
457                     }
458                 );
459             }
460         }
461 
462         items.push("-");
463         items.push(
464             {
465                 label: "net.label.Resend",
466                 tooltiptext: "net.tip.Resend",
467                 id: "fbNetResend",
468                 command: Obj.bindFixed(Firebug.Spy.XHR.resend, Firebug.Spy.XHR, file, this.context)
469             }
470         );
471 
472         return items;
473     },
474 
475     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
476     // Context menu commands
477 
478     copyURLParams: function(file)
479     {
480         var params = Url.parseURLParams(file.href);
481         var result = params.map(function(o) { return o.name + "=" + o.value; });
482         System.copyToClipboard(result.join(Str.lineBreak()));
483     },
484 
485     copyPOSTParams: function(file)
486     {
487         if (!NetUtils.isURLEncodedRequest(file, this.context))
488             return;
489 
490         var text = NetUtils.getPostText(file, this.context, true);
491         if (text)
492         {
493             var lines = text.split("\n");
494             var params = Url.parseURLEncodedText(lines[lines.length-1]);
495             var result = params.map(function(o) { return o.name + "=" + o.value; });
496             System.copyToClipboard(result.join(Str.lineBreak()));
497         }
498     },
499 
500     copyParams: function(file)
501     {
502         var text = NetUtils.getPostText(file, this.context, true);
503         var url = Url.reEncodeURL(file, text, true);
504         System.copyToClipboard(url);
505     },
506 
507     copyRequestHeaders: function(file)
508     {
509         System.copyToClipboard(file.requestHeadersText);
510     },
511 
512     copyResponseHeaders: function(file)
513     {
514         System.copyToClipboard(file.responseHeadersText);
515     },
516 
517     copyResponse: function(file)
518     {
519         // Copy response to the clipboard
520         System.copyToClipboard(NetUtils.getResponseText(file, this.context));
521     },
522 
523     openRequestInTab: function(file)
524     {
525         if (file.postText)
526         {
527             var lines = file.postText.split("\n");
528             Win.openNewTab(file.href, lines[lines.length-1]);
529         }
530         else
531         {
532             Win.openNewTab(file.href, null);
533         }
534     },
535 
536     breakOnRequest: function(file)
537     {
538         if (!file.isXHR)
539             return;
540 
541         // Create new or remove an existing breakpoint.
542         var breakpoints = this.context.netProgress.breakpoints;
543         var url = file.getFileURL();
544         var bp = breakpoints.findBreakpoint(url);
545         if (bp)
546             breakpoints.removeBreakpoint(url);
547         else
548             breakpoints.addBreakpoint(url);
549 
550         this.enumerateRequests(function(currFile)
551         {
552             if (url != currFile.getFileURL())
553                 return;
554 
555             if (bp)
556                 currFile.row.removeAttribute("breakpoint");
557             else
558                 currFile.row.setAttribute("breakpoint", "true");
559         })
560     },
561 
562     stopLoading: function(file)
563     {
564         const NS_BINDING_ABORTED = 0x804b0002;
565 
566         file.request.cancel(NS_BINDING_ABORTED);
567     },
568 
569     // Support for xhr breakpoint conditions.
570     onContextMenu: function(event)
571     {
572         if (!Css.hasClass(event.target, "sourceLine"))
573             return;
574 
575         var row = Dom.getAncestorByClass(event.target, "netRow");
576         if (!row)
577             return;
578 
579         var file = row.repObject;
580         var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
581         if (!bp)
582             return;
583 
584         this.editBreakpointCondition(file);
585         Events.cancelEvent(event);
586     },
587 
588     editBreakpointCondition: function(file)
589     {
590         var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
591         if (!bp)
592             return;
593 
594         var condition = bp ? bp.condition : "";
595 
596         this.selectedSourceBox = this.panelNode;
597         Firebug.Editor.startEditing(file.row, condition);
598     },
599 
600     getEditor: function(target, value)
601     {
602         if (!this.conditionEditor)
603             this.conditionEditor = new Firebug.NetMonitor.ConditionEditor(this.document);
604 
605         return this.conditionEditor;
606     },
607 
608     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
609     // Activable Panel
610 
611     /**
612      * Support for panel activation.
613      */
614     onActivationChanged: function(enable)
615     {
616         if (FBTrace.DBG_NET || FBTrace.DBG_ACTIVATION)
617             FBTrace.sysout("net.NetPanel.onActivationChanged; enable: " + enable);
618 
619         if (enable)
620         {
621             Firebug.NetMonitor.addObserver(this);
622             Firebug.TabCacheModel.addObserver(this);
623         }
624         else
625         {
626             Firebug.NetMonitor.removeObserver(this);
627             Firebug.TabCacheModel.removeObserver(this);
628         }
629     },
630 
631     breakOnNext: function(breaking)
632     {
633         this.context.breakOnXHR = breaking;
634     },
635 
636     shouldBreakOnNext: function()
637     {
638         return this.context.breakOnXHR;
639     },
640 
641     getBreakOnNextTooltip: function(enabled)
642     {
643         return (enabled ? Locale.$STR("net.Disable Break On XHR") : Locale.$STR("net.Break On XHR"));
644     },
645 
646     // Support for info tips.
647     showInfoTip: function(infoTip, target, x, y)
648     {
649         var row = Dom.getAncestorByClass(target, "netRow");
650         if (row && row.repObject)
651         {
652             if (Dom.getAncestorByClass(target, "netTotalSizeCol"))
653             {
654                 var infoTipURL = "netTotalSize";
655                 if (infoTipURL == this.infoTipURL)
656                     return true;
657 
658                 this.infoTipURL = infoTipURL;
659                 return this.populateTotalSizeInfoTip(infoTip, row);
660             }
661             else if (Dom.getAncestorByClass(target, "netSizeCol"))
662             {
663                 var infoTipURL = row.repObject.href + "-netsize";
664                 if (infoTipURL == this.infoTipURL && row.repObject == this.infoTipFile)
665                     return true;
666 
667                 this.infoTipURL = infoTipURL;
668                 this.infoTipFile = row.repObject;
669                 return this.populateSizeInfoTip(infoTip, row.repObject);
670             }
671             else if (Dom.getAncestorByClass(target, "netTimeCol"))
672             {
673                 var infoTipURL = row.repObject.href + "-nettime";
674                 if (infoTipURL == this.infoTipURL && row.repObject == this.infoTipFile)
675                     return true;
676 
677                 this.infoTipURL = infoTipURL;
678                 this.infoTipFile = row.repObject;
679                 return this.populateTimeInfoTip(infoTip, row.repObject);
680             }
681             else if (Css.hasClass(row, "category-image") &&
682                 !Dom.getAncestorByClass(target, "netRowHeader"))
683             {
684                 var infoTipURL = row.repObject.href + "-image";
685                 if (infoTipURL == this.infoTipURL)
686                     return true;
687 
688                 this.infoTipURL = infoTipURL;
689                 return CSSInfoTip.populateImageInfoTip(infoTip, row.repObject.href);
690             }
691         }
692 
693         delete this.infoTipURL;
694         return false;
695     },
696 
697     populateTimeInfoTip: function(infoTip, file)
698     {
699         Firebug.NetMonitor.TimeInfoTip.render(this.context, file, infoTip);
700         return true;
701     },
702 
703     populateSizeInfoTip: function(infoTip, file)
704     {
705         Firebug.NetMonitor.SizeInfoTip.render(file, infoTip);
706         return true;
707     },
708 
709     populateTotalSizeInfoTip: function(infoTip, row)
710     {
711         var totalSizeLabel = row.getElementsByClassName("netTotalSizeLabel").item(0);
712         var file = {size: totalSizeLabel.getAttribute("totalSize")};
713         Firebug.NetMonitor.SizeInfoTip.tag.replace({file: file}, infoTip);
714         return true;
715     },
716 
717     // Support for search within the panel.
718     getSearchOptionsMenuItems: function()
719     {
720         return [
721             Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive",
722                 "search.tip.Case_Sensitive"),
723             //Firebug.Search.searchOptionMenu("search.net.Headers", "netSearchHeaders"),
724             //Firebug.Search.searchOptionMenu("search.net.Parameters", "netSearchParameters"),
725             Firebug.Search.searchOptionMenu("search.Use_Regular_Expression",
726                 "searchUseRegularExpression", "search.tip.Use_Regular_Expression"),
727             Firebug.Search.searchOptionMenu("search.net.Response_Bodies", "netSearchResponseBody",
728                 "search.net.tip.Response_Bodies")
729         ];
730     },
731 
732     search: function(text, reverse)
733     {
734         if (!text)
735         {
736             delete this.currentSearch;
737             this.highlightNode(null);
738             return false;
739         }
740 
741         var row;
742         if (this.currentSearch && text == this.currentSearch.text)
743         {
744             row = this.currentSearch.findNext(true, false, reverse, Firebug.Search.isCaseSensitive(text));
745         }
746         else
747         {
748             this.currentSearch = new NetPanelSearch(this);
749             row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
750         }
751 
752         if (row)
753         {
754             var sel = this.document.defaultView.getSelection();
755             sel.removeAllRanges();
756             sel.addRange(this.currentSearch.range);
757 
758             Dom.scrollIntoCenterView(row, this.panelNode);
759             if(this.currentSearch.shouldSearchResponses() &&
760                 Dom.getAncestorByClass(row, "netInfoResponseText"))
761             {
762                 this.highlightNode(row)
763             }
764             else
765             {
766                 this.highlightNode(Dom.getAncestorByClass(row, "netRow"));
767             }
768             Events.dispatch(this.fbListeners, 'onNetMatchFound', [this, text, row]);
769             return true;
770         }
771         else
772         {
773             Events.dispatch(this.fbListeners, 'onNetMatchFound', [this, text, null]);
774             return false;
775         }
776     },
777 
778     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
779 
780     updateFile: function(file)
781     {
782         if (!file.invalid)
783         {
784             file.invalid = true;
785             this.queue.push(file);
786         }
787     },
788 
789     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
790 
791     updateLayout: function()
792     {
793         if (!this.queue.length)
794             return;
795 
796         var rightNow = NetUtils.now();
797         var length = this.queue.length;
798 
799         if (this.panelNode.offsetHeight)
800             this.wasScrolledToBottom = Dom.isScrolledToBottom(this.panelNode);
801 
802         this.layout();
803 
804         if (this.wasScrolledToBottom)
805             Dom.scrollToBottom(this.panelNode);
806 
807         this.updateHRefLabelWidth();
808 
809         if (FBTrace.DBG_NET)
810             FBTrace.sysout("net.updateLayout; Layout done, time elapsed: " +
811                 Str.formatTime(NetUtils.now() - rightNow) + " (" + length + ")");
812     },
813 
814     layout: function()
815     {
816         if (!this.queue.length || !this.context.netProgress ||
817             !Firebug.NetMonitor.isAlwaysEnabled())
818             return;
819 
820         this.initLayout();
821 
822         var rightNow = NetUtils.now();
823         this.updateRowData(rightNow);
824         this.updateLogLimit(Firebug.NetMonitor.maxQueueRequests);
825         this.updateTimeline(rightNow);
826         this.updateSummaries(rightNow);
827     },
828 
829     initLayout: function()
830     {
831         if (!this.table)
832         {
833             var limitInfo = {
834                 totalCount: 0,
835                 limitPrefsTitle: Locale.$STRF("LimitPrefsTitle",
836                     [Options.prefDomain+".net.logLimit"])
837             };
838 
839             this.table = Firebug.NetMonitor.NetRequestTable.tableTag.append({}, this.panelNode);
840             var tbody = this.table.querySelector(".netTableBody");
841             this.limitRow = Firebug.NetMonitor.NetLimit.createRow(tbody, limitInfo);
842             this.summaryRow = NetRequestEntry.summaryTag.insertRows({}, this.table.lastChild.lastChild)[0];
843 
844             NetRequestEntry.footerTag.insertRows({}, this.summaryRow);
845 
846             // Update visibility of columns according to the preferences
847             var hiddenCols = Options.get("net.hiddenColumns");
848             if (hiddenCols)
849                 this.table.setAttribute("hiddenCols", hiddenCols);
850 
851             this.updateBFCacheResponses();
852         }
853     },
854 
855     updateRowData: function(rightNow)
856     {
857         var queue = this.queue;
858         this.queue = [];
859 
860         var phase;
861         var newFileData = [];
862 
863         for (var i = 0; i < queue.length; ++i)
864         {
865             var file = queue[i];
866 
867             // xxxHonza: the entire phase management should ba part of NetPanel object
868             if (!file.phase && this.context.netProgress)
869                 this.context.netProgress.extendPhase(file);
870 
871             if (!file.phase)
872                 continue;
873 
874             file.invalid = false;
875 
876             phase = this.calculateFileTimes(file, phase, rightNow);
877 
878             this.updateFileRow(file, newFileData);
879             this.invalidatePhase(phase);
880         }
881 
882         if (newFileData.length)
883         {
884             var tbody = this.table.querySelector(".netTableBody");
885             var lastRow = this.summaryRow.previousSibling;
886             this.insertRows(newFileData, lastRow);
887         }
888     },
889 
890     insertRows: function(files, lastRow)
891     {
892         var row = NetRequestEntry.fileTag.insertRows({files: files}, lastRow)[0];
893 
894         for (var i = 0; i < files.length; ++i)
895         {
896             var file = files[i].file;
897             row.repObject = file;
898             file.row = row;
899 
900             if (file.breakLayout)
901                 row.setAttribute("breakLayout", "true");
902 
903             // Make sure a breakpoint is displayed.
904             var breakpoints = this.context.netProgress.breakpoints;
905             if (breakpoints && breakpoints.findBreakpoint(file.getFileURL()))
906                 row.setAttribute("breakpoint", "true");
907 
908             // Allow customization of request entries in the list. A row is represented
909             // by <TR> HTML element.
910             Events.dispatch(Firebug.NetMonitor.NetRequestTable.fbListeners,
911                 "onCreateRequestEntry", [this, row]);
912 
913             row = row.nextSibling;
914         }
915     },
916 
917     invalidatePhase: function(phase)
918     {
919         if (phase && !phase.invalidPhase)
920         {
921             phase.invalidPhase = true;
922             this.invalidPhases = true;
923         }
924     },
925 
926     updateFileRow: function(file, newFileData)
927     {
928         var row = file.row;
929         if (!row)
930         {
931             newFileData.push({
932                 file: file,
933                 offset: this.barOffset + "%",
934                 width: this.barReceivingWidth + "%",
935                 elapsed: file.loaded ? this.elapsed : -1
936             });
937         }
938         else
939         {
940             var sizeLabel = row.getElementsByClassName("netSizeLabel").item(0);
941 
942             var sizeText = NetRequestEntry.getSize(file);
943 
944             // Show also total downloaded size for requests in progress.
945             if (file.totalReceived)
946                 sizeText += " (" + Str.formatSize(file.totalReceived) + ")";
947 
948             sizeLabel.firstChild.nodeValue = sizeText;
949 
950             var methodLabel = row.getElementsByClassName("netStatusLabel").item(0);
951             methodLabel.firstChild.nodeValue = NetRequestEntry.getStatus(file);
952 
953             var hrefLabel = row.getElementsByClassName("netHrefLabel").item(0);
954             hrefLabel.firstChild.nodeValue = NetRequestEntry.getHref(file);
955 
956             if (file.mimeType)
957             {
958                 // Force update category.
959                 file.category = null;
960                 for (var category in NetUtils.fileCategories)
961                     Css.removeClass(row, "category-" + category);
962                 Css.setClass(row, "category-" + NetUtils.getFileCategory(file));
963             }
964 
965             var remoteIPLabel = row.querySelector(".netRemoteAddressCol .netAddressLabel");
966             remoteIPLabel.textContent = NetRequestEntry.getRemoteAddress(file);
967 
968             var localIPLabel = row.querySelector(".netLocalAddressCol .netAddressLabel");
969             localIPLabel.textContent = NetRequestEntry.getLocalAddress(file);
970 
971             if (file.requestHeaders)
972                 Css.setClass(row, "hasHeaders");
973 
974             if (file.fromCache)
975                 Css.setClass(row, "fromCache");
976             else
977                 Css.removeClass(row, "fromCache");
978 
979             if (file.fromBFCache)
980                 Css.setClass(row, "fromBFCache");
981             else
982                 Css.removeClass(row, "fromBFCache");
983 
984             if (NetRequestEntry.isError(file))
985                 Css.setClass(row, "responseError");
986             else
987                 Css.removeClass(row, "responseError");
988 
989             var netBar = Dom.getChildByClass(row, "netTimeCol").childNodes[1];
990             var timeLabel = Dom.getChildByClass(netBar, "netReceivingBar").firstChild;
991             timeLabel.textContent = NetRequestEntry.getElapsedTime({elapsed: this.elapsed});
992 
993             if (file.loaded)
994                 Css.setClass(row, "loaded");
995             else
996                 Css.removeClass(row, "loaded");
997 
998             if (Css.hasClass(row, "opened"))
999             {
1000                 var netInfoBox = row.nextSibling.getElementsByClassName("netInfoBody").item(0);
1001                 Firebug.NetMonitor.NetInfoBody.updateInfo(netInfoBox, file, this.context);
1002             }
1003         }
1004     },
1005 
1006     updateTimeline: function(rightNow)
1007     {
1008         var tbody = this.table.querySelector(".netTableBody");
1009 
1010         // XXXjoe Don't update rows whose phase is done and layed out already
1011         var phase;
1012         for (var row = tbody.firstChild; row; row = row.nextSibling)
1013         {
1014             var file = row.repObject;
1015 
1016             // Some rows aren't associated with a file (e.g. header, sumarry).
1017             if (!file)
1018                 continue;
1019 
1020             if (!file.loaded)
1021                 continue;
1022 
1023             phase = this.calculateFileTimes(file, phase, rightNow);
1024 
1025             // Parent node for all timing bars.
1026             var netBar = row.querySelector(".netBar");
1027 
1028             // Get bar nodes
1029             var blockingBar = netBar.childNodes[1];
1030             var resolvingBar = blockingBar.nextSibling;
1031             var connectingBar = resolvingBar.nextSibling;
1032             var sendingBar = connectingBar.nextSibling;
1033             var waitingBar = sendingBar.nextSibling;
1034             var receivingBar = waitingBar.nextSibling;
1035 
1036             // All bars starts at the beginning
1037             resolvingBar.style.left = connectingBar.style.left = sendingBar.style.left =
1038                 blockingBar.style.left =
1039                 waitingBar.style.left = receivingBar.style.left = this.barOffset + "%";
1040 
1041             // Sets width of all bars (using style). The width is computed according to measured timing.
1042             blockingBar.style.width = this.barBlockingWidth + "%";
1043             resolvingBar.style.width = this.barResolvingWidth + "%";
1044             connectingBar.style.width = this.barConnectingWidth + "%";
1045             sendingBar.style.width = this.barSendingWidth + "%";
1046             waitingBar.style.width = this.barWaitingWidth + "%";
1047             receivingBar.style.width = this.barReceivingWidth + "%";
1048 
1049             // Remove existing bars
1050             var bars = netBar.querySelectorAll(".netPageTimingBar");
1051             for (var i=0; i<bars.length; i++)
1052                 bars[i].parentNode.removeChild(bars[i]);
1053 
1054             // Generate UI for page timings (vertical lines displayed for the first phase)
1055             for (var i=0; i<phase.timeStamps.length; i++)
1056             {
1057                 var timing = phase.timeStamps[i];
1058                 if (!timing.offset)
1059                     continue;
1060 
1061                 var bar = netBar.ownerDocument.createElement("DIV");
1062                 netBar.appendChild(bar);
1063 
1064                 if (timing.classes)
1065                     Css.setClass(bar, timing.classes);
1066 
1067                 Css.setClass(bar, "netPageTimingBar");
1068 
1069                 bar.style.left = timing.offset + "%";
1070                 bar.style.display = "block";
1071             }
1072         }
1073     },
1074 
1075     calculateFileTimes: function(file, phase, rightNow)
1076     {
1077         var phases = this.context.netProgress.phases;
1078 
1079         if (phase != file.phase)
1080         {
1081             phase = file.phase;
1082             this.phaseStartTime = phase.startTime;
1083             this.phaseEndTime = phase.endTime ? phase.endTime : rightNow;
1084 
1085             // End of the first phase has to respect even the window "onload" event time, which
1086             // can occur after the last received file. This sets the extent of the timeline so,
1087             // the windowLoadBar is visible.
1088             if (phase.windowLoadTime && this.phaseEndTime < phase.windowLoadTime)
1089                 this.phaseEndTime = phase.windowLoadTime;
1090 
1091             this.phaseElapsed = this.phaseEndTime - phase.startTime;
1092         }
1093 
1094         var elapsed = file.loaded ? file.endTime - file.startTime : 0; /*this.phaseEndTime - file.startTime*/
1095         this.barOffset = Math.floor(((file.startTime-this.phaseStartTime)/this.phaseElapsed) * 100);
1096 
1097         //Helper log for debugging timing problems.
1098         //NetUtils.traceRequestTiming("net.calculateFileTimes;", file);
1099 
1100         var blockingEnd = NetUtils.getBlockingEndTime(file);
1101         this.barBlockingWidth = Math.round(((blockingEnd - file.startTime) / this.phaseElapsed) * 100);
1102         this.barResolvingWidth = Math.round(((file.connectingTime - file.startTime) / this.phaseElapsed) * 100);
1103         this.barConnectingWidth = Math.round(((file.sendingTime - file.startTime) / this.phaseElapsed) * 100);
1104         this.barSendingWidth = Math.round(((file.waitingForTime - file.startTime) / this.phaseElapsed) * 100);
1105         this.barWaitingWidth = Math.round(((file.respondedTime - file.startTime) / this.phaseElapsed) * 100);
1106         this.barReceivingWidth = Math.round((elapsed / this.phaseElapsed) * 100);
1107 
1108         // Total request time doesn't include the time spent in queue.
1109         // xxxHonza: since all phases are now graphically distinguished it's easy to
1110         // see blocking requests. It's make sense to display the real total time now.
1111         this.elapsed = elapsed/* - (file.sendingTime - file.connectedTime)*/;
1112 
1113         // The nspr timer doesn't have 1ms precision, so it can happen that entire
1114         // request is executed in l ms (so the total is zero). Let's display at least
1115         // one bar in such a case so the timeline is visible.
1116         if (this.elapsed <= 0)
1117             this.barReceivingWidth = "1";
1118 
1119         // Compute also offset for page timings, e.g.: contentLoadBar and windowLoadBar,
1120         // which are displayed for the first phase. This is done only if a page exists.
1121         this.calculateTimeStamps(file, phase);
1122 
1123         return phase;
1124     },
1125 
1126     calculateTimeStamps: function(file, phase)
1127     {
1128         // Iterate all time stamps for the current phase and calculate offsets (from the
1129         // beginning of the waterfall graphs) for the vertical lines.
1130         for (var i=0; i<phase.timeStamps.length; i++)
1131         {
1132             var timeStamp = phase.timeStamps[i];
1133             var time = timeStamp.time;
1134 
1135             if (time > 0)
1136             {
1137                 var offset = (((time - this.phaseStartTime)/this.phaseElapsed) * 100).toFixed(3);
1138                 timeStamp.offset = offset;
1139             }
1140          }
1141     },
1142 
1143     updateSummaries: function(rightNow, updateAll)
1144     {
1145         if (!this.invalidPhases && !updateAll)
1146             return;
1147 
1148         this.invalidPhases = false;
1149 
1150         var phases = this.context.netProgress.phases;
1151         if (!phases.length)
1152             return;
1153 
1154         var fileCount = 0, totalSize = 0, cachedSize = 0, totalTime = 0;
1155         for (var i = 0; i < phases.length; ++i)
1156         {
1157             var phase = phases[i];
1158             phase.invalidPhase = false;
1159 
1160             var summary = this.summarizePhase(phase, rightNow);
1161             fileCount += summary.fileCount;
1162             totalSize += summary.totalSize;
1163             cachedSize += summary.cachedSize;
1164             totalTime += summary.totalTime
1165         }
1166 
1167         var row = this.summaryRow;
1168         if (!row)
1169             return;
1170 
1171         var countLabel = row.getElementsByClassName("netCountLabel").item(0); //childNodes[1].firstChild;
1172         countLabel.firstChild.nodeValue = Locale.$STRP("plural.Request_Count2", [fileCount]);
1173 
1174         var sizeLabel = row.getElementsByClassName("netTotalSizeLabel").item(0); //childNodes[4].firstChild;
1175         sizeLabel.setAttribute("totalSize", totalSize);
1176         sizeLabel.firstChild.nodeValue = NetRequestEntry.formatSize(totalSize);
1177 
1178         var cacheSizeLabel = row.getElementsByClassName("netCacheSizeLabel").item(0);
1179         cacheSizeLabel.setAttribute("collapsed", cachedSize == 0);
1180         cacheSizeLabel.childNodes[1].firstChild.nodeValue =
1181             NetRequestEntry.formatSize(cachedSize);
1182 
1183         var timeLabel = row.getElementsByClassName("netTotalTimeLabel").item(0);
1184         var timeText = NetRequestEntry.formatTime(totalTime);
1185         var firstPhase = phases[0];
1186         if (firstPhase.windowLoadTime)
1187         {
1188             var loadTime = firstPhase.windowLoadTime - firstPhase.startTime;
1189             timeText += " (onload: " + NetRequestEntry.formatTime(loadTime) + ")";
1190         }
1191 
1192         timeLabel.textContent = timeText;
1193     },
1194 
1195     summarizePhase: function(phase, rightNow)
1196     {
1197         var cachedSize = 0, totalSize = 0;
1198 
1199         var category = Firebug.netFilterCategory;
1200         if (category == "all")
1201             category = null;
1202 
1203         var fileCount = 0;
1204         var minTime = 0, maxTime = 0;
1205 
1206         for (var i=0; i<phase.files.length; i++)
1207         {
1208             var file = phase.files[i];
1209 
1210             // Do not count BFCache responses if the user says so.
1211             if (!Firebug.netShowBFCacheResponses && file.fromBFCache)
1212                 continue;
1213 
1214             if (!category || file.category == category)
1215             {
1216                 if (file.loaded)
1217                 {
1218                     ++fileCount;
1219 
1220                     if (file.size > 0)
1221                     {
1222                         totalSize += file.size;
1223                         if (file.fromCache)
1224                             cachedSize += file.size;
1225                     }
1226 
1227                     if (!minTime || file.startTime < minTime)
1228                         minTime = file.startTime;
1229                     if (file.endTime > maxTime)
1230                         maxTime = file.endTime;
1231                 }
1232             }
1233         }
1234 
1235         var totalTime = maxTime - minTime;
1236         return {cachedSize: cachedSize, totalSize: totalSize, totalTime: totalTime,
1237                 fileCount: fileCount}
1238     },
1239 
1240     updateLogLimit: function(limit)
1241     {
1242         var netProgress = this.context.netProgress;
1243 
1244         if (!netProgress)  // XXXjjb Honza, please check, I guess we are getting here with the context not setup
1245         {
1246             if (FBTrace.DBG_NET)
1247                 FBTrace.sysout("net.updateLogLimit; NO NET CONTEXT for: " + this.context.getName());
1248             return;
1249         }
1250 
1251         // Must be positive number;
1252         limit = Math.max(0, limit);
1253 
1254         var filesLength = netProgress.files.length;
1255         if (!filesLength || filesLength <= limit)
1256             return;
1257 
1258         // Remove old requests.
1259         var removeCount = Math.max(0, filesLength - limit);
1260         for (var i=0; i<removeCount; i++)
1261         {
1262             var file = netProgress.files[0];
1263             this.removeLogEntry(file);
1264 
1265             // Remove the file occurrence from the queue.
1266             for (var j=0; j<this.queue.length; j++)
1267             {
1268                 if (this.queue[j] == file) {
1269                     this.queue.splice(j, 1);
1270                     j--;
1271                 }
1272             }
1273         }
1274     },
1275 
1276     removeLogEntry: function(file, noInfo)
1277     {
1278         // Remove associated row-entry from the UI before the removeFile method
1279         // is called (and file.row erased).
1280         if (this.table)
1281         {
1282             var tbody = this.table.querySelector(".netTableBody");
1283             if (tbody && file.row)
1284                 tbody.removeChild(file.row);
1285         }
1286 
1287         if (!this.removeFile(file))
1288             return;
1289 
1290         if (!this.table)
1291             return;
1292 
1293         var tbody = this.table.querySelector(".netTableBody");
1294         if (!tbody)
1295             return;
1296 
1297         if (noInfo || !this.limitRow)
1298             return;
1299 
1300         this.limitRow.limitInfo.totalCount++;
1301 
1302         Firebug.NetMonitor.NetLimit.updateCounter(this.limitRow);
1303 
1304         //if (netProgress.currentPhase == file.phase)
1305         //  netProgress.currentPhase = null;
1306     },
1307 
1308     removeFile: function(file)
1309     {
1310         var netProgress = this.context.netProgress;
1311         var index = netProgress.files.indexOf(file);
1312         if (index == -1)
1313             return false;
1314 
1315         netProgress.files.splice(index, 1);
1316         netProgress.requests.splice(index, 1);
1317 
1318         // Don't forget to remove the phase whose last file has been removed.
1319         var phase = file.phase;
1320 
1321         // xxxHonza: This needs to be examined yet. Looks like the queue contains
1322         // requests from the previous page. When flushed the requestedFile isn't called
1323         // and the phase is not set.
1324         if (!phase)
1325             return true;
1326 
1327         phase.removeFile(file);
1328         if (!phase.files.length)
1329         {
1330             Arr.remove(netProgress.phases, phase);
1331 
1332             if (netProgress.currentPhase == phase)
1333                 netProgress.currentPhase = null;
1334         }
1335 
1336         file.clear();
1337 
1338         return true;
1339     },
1340 
1341     insertActivationMessage: function()
1342     {
1343         if (!Firebug.NetMonitor.isAlwaysEnabled())
1344             return;
1345 
1346         // Make sure the basic structure of the table panel is there.
1347         this.initLayout();
1348 
1349         // Get the last request row before summary row.
1350         var lastRow = this.summaryRow.previousSibling;
1351 
1352         // Insert an activation message (if the last row isn't the message already);
1353         if (Css.hasClass(lastRow, "netActivationRow"))
1354             return;
1355 
1356         var message = NetRequestEntry.activationTag.insertRows({}, lastRow)[0];
1357 
1358         if (FBTrace.DBG_NET)
1359             FBTrace.sysout("net.insertActivationMessage; " + this.context.getName(), message);
1360     },
1361 
1362     enumerateRequests: function(fn)
1363     {
1364         if (!this.table)
1365             return;
1366 
1367         var rows = this.table.getElementsByClassName("netRow");
1368         for (var i=0; i<rows.length; i++)
1369         {
1370             var row = rows[i];
1371             var pageRow = Css.hasClass(row, "netPageRow");
1372 
1373             if (Css.hasClass(row, "collapsed") && !pageRow)
1374                 continue;
1375 
1376             if (Css.hasClass(row, "history"))
1377                 continue;
1378 
1379             // Export also history. These requests can be collapsed and so not visible.
1380             if (row.files)
1381             {
1382                 for (var j=0; j<row.files.length; j++)
1383                     fn(row.files[j].file);
1384             }
1385 
1386             var file = Firebug.getRepObject(row);
1387             if (file)
1388                 fn(file);
1389         }
1390     },
1391 
1392     setFilter: function(filterCategory)
1393     {
1394         this.filterCategory = filterCategory;
1395 
1396         var panelNode = this.panelNode;
1397         for (var category in NetUtils.fileCategories)
1398         {
1399             if (filterCategory != "all" && category != filterCategory)
1400                 Css.setClass(panelNode, "hideCategory-"+category);
1401             else
1402                 Css.removeClass(panelNode, "hideCategory-"+category);
1403         }
1404     },
1405 
1406     clear: function()
1407     {
1408         Dom.clearNode(this.panelNode);
1409 
1410         this.table = null;
1411         this.summaryRow = null;
1412         this.limitRow = null;
1413 
1414         this.queue = [];
1415         this.invalidPhases = false;
1416 
1417         if (this.context.netProgress)
1418             this.context.netProgress.clear();
1419 
1420         if (FBTrace.DBG_NET)
1421             FBTrace.sysout("net.panel.clear; " + this.context.getName());
1422     },
1423 
1424     onResize: function()
1425     {
1426         this.updateHRefLabelWidth();
1427     },
1428 
1429     updateHRefLabelWidth: function()
1430     {
1431         if (!this.table)
1432             return;
1433 
1434         // Update max-width of the netHrefLabel according to the width of the parent column.
1435         // I don't know if there is a way to do this in Css.
1436         // See Issue 3633: Truncated URLs in net panel
1437         var netHrefCol = this.table.querySelector("#netHrefCol");
1438         var hrefLabel = this.table.querySelector(".netHrefLabel");
1439 
1440         if (!hrefLabel)
1441             return;
1442 
1443         if (!Firebug.currentContext)
1444         {
1445             if (FBTrace.DBG_ERRORS)
1446                 FBTrace.sysout("net.updateHRefLabelWidth; Firebug.currentContext == NULL");
1447             return;
1448         }
1449 
1450         var maxWidth = netHrefCol.clientWidth;
1451 
1452         // This call must precede all getCSSStyleRules calls  FIXME not needed after 3.6
1453         Firebug.CSSModule.cleanupSheets(hrefLabel.ownerDocument, this.context);
1454         var rules = Dom.domUtils.getCSSStyleRules(hrefLabel);
1455         for (var i = 0; i < rules.Count(); ++i)
1456         {
1457             var rule = Xpcom.QI(rules.GetElementAt(i), Ci.nsIDOMCSSStyleRule);
1458             if (rule.selectorText == ".netHrefLabel")
1459             {
1460                 var style = rule.style;
1461                 var paddingLeft = parseInt(style.getPropertyValue("padding-left"));
1462                 if (maxWidth == 0)
1463                     style.setProperty("max-width", "15%", "");
1464                 else
1465                     style.setProperty("max-width", (maxWidth - paddingLeft) + "px", "");
1466                 break;
1467             }
1468         }
1469     },
1470 });
1471 
1472 // ********************************************************************************************* //
1473 
1474 /*
1475  * Use this object to automatically select Net panel and inspect a network request.
1476  * Firebug.chrome.select(new Firebug.NetMonitor.NetFileLink(url [, request]));
1477  */
1478 Firebug.NetMonitor.NetFileLink = function(href, request)
1479 {
1480     this.href = href;
1481     this.request = request;
1482 }
1483 
1484 Firebug.NetMonitor.NetFileLink.prototype =
1485 {
1486     toString: function()
1487     {
1488         return this.message + this.href;
1489     }
1490 };
1491 
1492 // ********************************************************************************************* //
1493 
1494 var NetPanelSearch = function(panel, rowFinder)
1495 {
1496     var panelNode = panel.panelNode;
1497     var doc = panelNode.ownerDocument;
1498     var searchRange, startPt;
1499 
1500     // Common search object methods.
1501     this.find = function(text, reverse, caseSensitive)
1502     {
1503         this.text = text;
1504 
1505         Search.finder.findBackwards = !!reverse;
1506         Search.finder.caseSensitive = !!caseSensitive;
1507 
1508         this.currentRow = this.getFirstRow();
1509         this.resetRange();
1510 
1511         return this.findNext(false, false, reverse, caseSensitive);
1512     };
1513 
1514     this.findNext = function(wrapAround, sameNode, reverse, caseSensitive)
1515     {
1516         while (this.currentRow)
1517         {
1518             var match = this.findNextInRange(reverse, caseSensitive);
1519             if (match)
1520                 return match;
1521 
1522             if (this.shouldSearchResponses())
1523                 this.findNextInResponse(reverse, caseSensitive);
1524 
1525             this.currentRow = this.getNextRow(wrapAround, reverse);
1526 
1527             if (this.currentRow)
1528                 this.resetRange();
1529         }
1530     };
1531 
1532     // Internal search helpers.
1533     this.findNextInRange = function(reverse, caseSensitive)
1534     {
1535         if (this.range)
1536         {
1537             startPt = doc.createRange();
1538             if (reverse)
1539                 startPt.setStartBefore(this.currentNode);
1540             else
1541                 startPt.setStart(this.currentNode, this.range.endOffset);
1542 
1543             this.range = Search.finder.Find(this.text, searchRange, startPt, searchRange);
1544             if (this.range)
1545             {
1546                 this.currentNode = this.range ? this.range.startContainer : null;
1547                 return this.currentNode ? this.currentNode.parentNode : null;
1548             }
1549         }
1550 
1551         if (this.currentNode)
1552         {
1553             startPt = doc.createRange();
1554             if (reverse)
1555                 startPt.setStartBefore(this.currentNode);
1556             else
1557                 startPt.setStartAfter(this.currentNode);
1558         }
1559 
1560         this.range = Search.finder.Find(this.text, searchRange, startPt, searchRange);
1561         this.currentNode = this.range ? this.range.startContainer : null;
1562         return this.currentNode ? this.currentNode.parentNode : null;
1563     },
1564 
1565     this.findNextInResponse = function(reverse, caseSensitive)
1566     {
1567         var file = Firebug.getRepObject(this.currentRow);
1568         if (!file)
1569             return;
1570 
1571         var scanRE = Firebug.Search.getTestingRegex(this.text);
1572         if (scanRE.test(file.responseText))
1573         {
1574             if (!Css.hasClass(this.currentRow, "opened"))
1575                 NetRequestEntry.toggleHeadersRow(this.currentRow);
1576 
1577             var netInfoRow = this.currentRow.nextSibling;
1578             var netInfoBox = netInfoRow.getElementsByClassName("netInfoBody").item(0);
1579             Firebug.NetMonitor.NetInfoBody.selectTabByName(netInfoBox, "Response");
1580 
1581             // Before the search is started, the new content must be properly
1582             // layouted within the page. The layout is executed by reading
1583             // the following property.
1584             // xxxHonza: This workaround can be removed as soon as #488427 is fixed.
1585             doc.body.offsetWidth;
1586         }
1587     },
1588 
1589     // Helpers
1590     this.resetRange = function()
1591     {
1592         searchRange = doc.createRange();
1593         searchRange.setStart(this.currentRow, 0);
1594         searchRange.setEnd(this.currentRow, this.currentRow.childNodes.length);
1595 
1596         startPt = searchRange;
1597     }
1598 
1599     this.getFirstRow = function()
1600     {
1601         var table = panelNode.getElementsByClassName("netTable").item(0);
1602         return table.querySelector(".netTableBody").firstChild;
1603     }
1604 
1605     this.getNextRow = function(wrapAround, reverse)
1606     {
1607         // xxxHonza: reverse searching missing.
1608         for (var sib = this.currentRow.nextSibling; sib; sib = sib.nextSibling)
1609         {
1610             if (this.shouldSearchResponses())
1611                 return sib;
1612             else if (Css.hasClass(sib, "netRow"))
1613                 return sib;
1614         }
1615 
1616         return wrapAround ? this.getFirstRow() : null;
1617     }
1618 
1619     this.shouldSearchResponses = function()
1620     {
1621         return Firebug["netSearchResponseBody"];
1622     }
1623 };
1624 
1625 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1626 
1627 Firebug.NetMonitor.ConditionEditor = function(doc)
1628 {
1629     Firebug.Breakpoint.ConditionEditor.apply(this, arguments);
1630 }
1631 
1632 Firebug.NetMonitor.ConditionEditor.prototype = domplate(Firebug.Breakpoint.ConditionEditor.prototype,
1633 {
1634     endEditing: function(target, value, cancel)
1635     {
1636         if (cancel)
1637             return;
1638 
1639         var file = target.repObject;
1640         var panel = Firebug.getElementPanel(target);
1641         var bp = panel.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
1642         if (bp)
1643             bp.condition = value;
1644     }
1645 });
1646 
1647 // ********************************************************************************************* //
1648 // Browser Cache
1649 
1650 Firebug.NetMonitor.BrowserCache =
1651 {
1652     cacheDomain: "browser.cache",
1653 
1654     isEnabled: function()
1655     {
1656         var diskCache = Options.getPref(this.cacheDomain, "disk.enable");
1657         var memoryCache = Options.getPref(this.cacheDomain, "memory.enable");
1658         return diskCache && memoryCache;
1659     },
1660 
1661     toggle: function(state)
1662     {
1663         if (FBTrace.DBG_NET)
1664             FBTrace.sysout("net.BrowserCache.toggle; " + state);
1665 
1666         Options.setPref(this.cacheDomain, "disk.enable", state);
1667         Options.setPref(this.cacheDomain, "memory.enable", state);
1668     }
1669 }
1670 
1671 // ********************************************************************************************* //
1672 // Registration
1673 
1674 Firebug.registerPanel(NetPanel);
1675 
1676 return Firebug.NetMonitor;
1677 
1678 // ********************************************************************************************* //
1679 }});
1680