1 /* See license.txt for terms of usage */ 2 3 FBL.ns(function() { with (FBL) { 4 5 // ************************************************************************************************ 6 // Constants 7 8 const Cc = Components.classes; 9 const Ci = Components.interfaces; 10 const Cr = Components.results; 11 12 const CacheService = Cc["@mozilla.org/network/cache-service;1"]; 13 const ImgCache = Cc["@mozilla.org/image/cache;1"]; 14 const IOService = Cc["@mozilla.org/network/io-service;1"]; 15 const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2); 16 17 const NOTIFY_ALL = Ci.nsIWebProgress.NOTIFY_ALL; 18 19 const nsIWebProgressListener = Ci.nsIWebProgressListener; 20 const STATE_IS_WINDOW = nsIWebProgressListener.STATE_IS_WINDOW; 21 const STATE_IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT; 22 const STATE_IS_NETWORK = nsIWebProgressListener.STATE_IS_NETWORK; 23 const STATE_IS_REQUEST = nsIWebProgressListener.STATE_IS_REQUEST; 24 const STATE_START = nsIWebProgressListener.STATE_START; 25 const STATE_STOP = nsIWebProgressListener.STATE_STOP; 26 const STATE_TRANSFERRING = nsIWebProgressListener.STATE_TRANSFERRING; 27 28 const LOAD_BACKGROUND = Ci.nsIRequest.LOAD_BACKGROUND; 29 const LOAD_FROM_CACHE = Ci.nsIRequest.LOAD_FROM_CACHE; 30 const LOAD_DOCUMENT_URI = Ci.nsIChannel.LOAD_DOCUMENT_URI; 31 32 const NS_ERROR_CACHE_KEY_NOT_FOUND = 0x804B003D; 33 const NS_ERROR_CACHE_WAIT_FOR_VALIDATION = 0x804B0040; 34 35 var nsIHttpActivityObserver = Ci.nsIHttpActivityObserver; 36 var nsIHttpActivityObserver = Ci.nsIHttpActivityObserver; 37 var nsISocketTransport = Ci.nsISocketTransport; 38 39 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 40 41 const reIgnore = /about:|javascript:|resource:|chrome:|jar:/; 42 const reResponseStatus = /HTTP\/1\.\d\s(\d+)\s(.*)/; 43 const layoutInterval = 300; 44 const indentWidth = 18; 45 46 var cacheSession = null; 47 var contexts = new Array(); 48 var panelName = "net"; 49 var maxQueueRequests = 500; 50 var panelBar1 = $("fbPanelBar1"); 51 var activeRequests = []; 52 53 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 54 55 const mimeExtensionMap = 56 { 57 "txt": "text/plain", 58 "html": "text/html", 59 "htm": "text/html", 60 "xhtml": "text/html", 61 "xml": "text/xml", 62 "css": "text/css", 63 "js": "application/x-javascript", 64 "jss": "application/x-javascript", 65 "jpg": "image/jpg", 66 "jpeg": "image/jpeg", 67 "gif": "image/gif", 68 "png": "image/png", 69 "bmp": "image/bmp", 70 "swf": "application/x-shockwave-flash", 71 "flv": "video/x-flv" 72 }; 73 74 const fileCategories = 75 { 76 "undefined": 1, 77 "html": 1, 78 "css": 1, 79 "js": 1, 80 "xhr": 1, 81 "image": 1, 82 "flash": 1, 83 "txt": 1, 84 "bin": 1 85 }; 86 87 const textFileCategories = 88 { 89 "txt": 1, 90 "html": 1, 91 "xhr": 1, 92 "css": 1, 93 "js": 1 94 }; 95 96 const binaryFileCategories = 97 { 98 "bin": 1, 99 "flash": 1 100 }; 101 102 const mimeCategoryMap = 103 { 104 "text/plain": "txt", 105 "application/octet-stream": "bin", 106 "text/html": "html", 107 "text/xml": "html", 108 "application/xhtml+xml": "html", 109 "text/css": "css", 110 "application/x-javascript": "js", 111 "text/javascript": "js", 112 "application/javascript" : "js", 113 "image/jpeg": "image", 114 "image/jpg": "image", 115 "image/gif": "image", 116 "image/png": "image", 117 "image/bmp": "image", 118 "application/x-shockwave-flash": "flash", 119 "video/x-flv": "flash" 120 }; 121 122 const binaryCategoryMap = 123 { 124 "image": 1, 125 "flash" : 1 126 }; 127 128 // ************************************************************************************************ 129 130 /** 131 * @module Represents a module object for the Net panel. This object is derived 132 * from <code>Firebug.ActivableModule</code> in order to support activation (enable/disable). 133 * This allows to avoid (performance) expensive features if the functionality is not necessary 134 * for the user. 135 */ 136 Firebug.NetMonitor = extend(Firebug.ActivableModule, 137 { 138 dispatchName: "netMonitor", 139 clear: function(context) 140 { 141 // The user pressed a Clear button so, remove content of the panel... 142 var panel = context.getPanel(panelName, true); 143 if (panel) 144 panel.clear(); 145 }, 146 147 onToggleFilter: function(context, filterCategory) 148 { 149 if (!context.netProgress) 150 return; 151 152 Firebug.setPref(Firebug.prefDomain, "netFilterCategory", filterCategory); 153 154 // The content filter has been changed. Make sure that the content 155 // of the panel is updated (CSS is used to hide or show individual files). 156 var panel = context.getPanel(panelName, true); 157 if (panel) 158 { 159 panel.setFilter(filterCategory); 160 panel.updateSummaries(now(), true); 161 } 162 }, 163 164 syncFilterButtons: function(chrome) 165 { 166 var button = chrome.$("fbNetFilter-" + Firebug.netFilterCategory); 167 button.checked = true; 168 }, 169 170 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 171 // extends Module 172 173 initializeUI: function() 174 { 175 Firebug.ActivableModule.initializeUI.apply(this, arguments); 176 177 // Initialize max limit for logged requests. 178 NetLimit.updateMaxLimit(); 179 180 // Synchronize UI buttons with the current filter. 181 this.syncFilterButtons(FirebugChrome); 182 183 prefs.addObserver(Firebug.prefDomain, NetLimit, false); 184 }, 185 186 initialize: function() 187 { 188 this.panelName = panelName; 189 190 Firebug.ActivableModule.initialize.apply(this, arguments); 191 192 if (Firebug.TraceModule) 193 Firebug.TraceModule.addListener(this.TraceListener); 194 195 // HTTP observer must be registered now (and not in monitorContext, since if a 196 // page is opened in a new tab the top document request would be missed otherwise. 197 Firebug.NetMonitor.NetHttpObserver.registerObserver(); 198 NetHttpActivityObserver.registerObserver(); 199 200 Firebug.Debugger.addListener(this.DebuggerListener); 201 }, 202 203 internationalizeUI: function(doc) 204 { 205 var element = doc.getElementById("fbNetPersist"); 206 FBL.internationalize(element, "label"); 207 FBL.internationalize(element, "tooltiptext"); 208 }, 209 210 shutdown: function() 211 { 212 prefs.removeObserver(Firebug.prefDomain, this, false); 213 if (Firebug.TraceModule) 214 Firebug.TraceModule.removeListener(this.TraceListener); 215 216 Firebug.NetMonitor.NetHttpObserver.unregisterObserver(); 217 NetHttpActivityObserver.unregisterObserver(); 218 219 Firebug.Debugger.removeListener(this.DebuggerListener); 220 }, 221 222 initContext: function(context, persistedState) 223 { 224 Firebug.ActivableModule.initContext.apply(this, arguments); 225 226 if (FBTrace.DBG_NET) 227 FBTrace.sysout("net.initContext for: " + context.getName()); 228 229 if (context.window && 'addEventListener' in context.window) 230 { 231 var window = context.window; 232 233 // Register "load" listener in order to track window load time. 234 var onWindowLoadHandler = function() { 235 if (context.netProgress) 236 context.netProgress.post(windowLoad, [window, now()]); 237 window.removeEventListener("load", onWindowLoadHandler, true); 238 } 239 window.addEventListener("load", onWindowLoadHandler, true); 240 241 // Register "DOMContentLoaded" listener to track timing. 242 var onContentLoadHandler = function() { 243 if (context.netProgress) 244 context.netProgress.post(contentLoad, [window, now()]); 245 window.removeEventListener("DOMContentLoaded", onContentLoadHandler, true); 246 } 247 248 window.addEventListener("DOMContentLoaded", onContentLoadHandler, true); 249 } 250 251 if (Firebug.NetMonitor.isAlwaysEnabled()) 252 monitorContext(context); 253 254 if (context.netProgress) 255 { 256 // Load existing breakpoints 257 var persistedPanelState = getPersistedState(context, panelName); 258 if (persistedPanelState.breakpoints) 259 context.netProgress.breakpoints = persistedPanelState.breakpoints; 260 } 261 }, 262 263 reattachContext: function(browser, context) 264 { 265 Firebug.ActivableModule.reattachContext.apply(this, arguments); 266 this.syncFilterButtons(Firebug.chrome); 267 }, 268 269 destroyContext: function(context, persistedState) 270 { 271 Firebug.ActivableModule.destroyContext.apply(this, arguments); 272 273 if (context.netProgress) 274 { 275 // Remember existing breakpoints. 276 var persistedPanelState = getPersistedState(context, panelName); 277 persistedPanelState.breakpoints = context.netProgress.breakpoints; 278 } 279 280 if (Firebug.NetMonitor.isAlwaysEnabled()) 281 unmonitorContext(context); 282 }, 283 284 showContext: function(browser, context) 285 { 286 Firebug.ActivableModule.showContext.apply(this, arguments); 287 288 if (FBTrace.DBG_NET) 289 FBTrace.sysout("net.showContext; " + (context ? context.getName() : "NULL")); 290 }, 291 292 loadedContext: function(context) 293 { 294 if (FBTrace.DBG_NET) 295 FBTrace.sysout("net.loadedContext; Remove temp context (if not removed yet) " + tabId); 296 297 var tabId = Firebug.getTabIdForWindow(context.browser.contentWindow); 298 delete contexts[tabId]; 299 300 var netProgress = context.netProgress; 301 if (netProgress) 302 { 303 netProgress.loaded = true; 304 305 // Set Page title and id into all document objects. 306 for (var i=0; i<netProgress.documents.length; i++) 307 { 308 var doc = netProgress.documents[i]; 309 doc.id = context.uid; 310 doc.title = context.getTitle(); 311 } 312 } 313 }, 314 315 onEnabled: function(context) 316 { 317 if (FBTrace.DBG_NET) 318 FBTrace.sysout("net.onEnabled; "+context.getName()); 319 320 NetHttpActivityObserver.registerObserver(); 321 322 monitorContext(context); 323 }, 324 325 onDisabled: function(context) 326 { 327 if (FBTrace.DBG_NET) 328 FBTrace.sysout("net.onDisabled; "+context.getName()); 329 330 NetHttpActivityObserver.unregisterObserver(); 331 332 unmonitorContext(context); 333 }, 334 335 onResumeFirebug: function() 336 { 337 if (FBTrace.DBG_NET) 338 FBTrace.sysout("net.onResumeFirebug; "); 339 340 // Resume only if enabled. 341 if (Firebug.NetMonitor.isAlwaysEnabled()) 342 TabWatcher.iterateContexts(monitorContext); 343 }, 344 345 onSuspendFirebug: function() 346 { 347 if (FBTrace.DBG_NET) 348 FBTrace.sysout("net.onSuspendFirebug; "); 349 350 // Suspend only if enabled. 351 if (Firebug.NetMonitor.isAlwaysEnabled()) 352 TabWatcher.iterateContexts(unmonitorContext); 353 }, 354 355 togglePersist: function(context) 356 { 357 var panel = context.getPanel(panelName); 358 panel.persistContent = panel.persistContent ? false : true; 359 Firebug.chrome.setGlobalAttribute("cmd_togglePersistNet", "checked", panel.persistContent); 360 } 361 }); 362 363 // ************************************************************************************************ 364 365 /** 366 * @panel Represents a Firebug panel that displayes info about HTTP activity associated with 367 * the current page. This class is derived from <code>Firebug.ActivablePanel</code> in order 368 * to support activation (enable/disable). This allows to avoid (performance) expensive 369 * features if the functionality is not necessary for the user. 370 */ 371 function NetPanel() {} 372 NetPanel.prototype = extend(Firebug.ActivablePanel, 373 { 374 name: panelName, 375 searchable: true, 376 editable: true, 377 breakable: true, 378 379 initialize: function(context, doc) 380 { 381 this.queue = []; 382 383 if (FBTrace.DBG_NET) 384 FBTrace.sysout("net.NetPanel.initialize; " + context.getName()); 385 386 // we listen for showUI/hideUI for panel activation 387 Firebug.registerUIListener(this); 388 389 this.onContextMenu = bind(this.onContextMenu, this); 390 391 Firebug.ActivablePanel.initialize.apply(this, arguments); 392 }, 393 394 destroy: function(state) 395 { 396 Firebug.ActivablePanel.destroy.apply(this, arguments); 397 398 Firebug.unregisterUIListener(this); 399 }, 400 401 initializeNode : function() 402 { 403 this.panelNode.addEventListener("contextmenu", this.onContextMenu, false); 404 405 dispatch([Firebug.A11yModel], "onInitializeNode", [this]); 406 }, 407 408 destroyNode : function() 409 { 410 this.panelNode.removeEventListener("contextmenu", this.onContextMenu, false); 411 412 dispatch([Firebug.A11yModel], "onDestroyNode", [this]); 413 }, 414 415 loadPersistedContent: function(state) 416 { 417 this.initLayout(); 418 419 var tbody = this.table.firstChild; 420 var lastRow = tbody.lastChild.previousSibling; 421 422 // Move all net-rows from the persistedState to this panel. 423 var prevTableBody = state.panelNode.getElementsByClassName("netTableBody").item(0); 424 if (!prevTableBody) 425 return; 426 427 var files = []; 428 429 while (prevTableBody.firstChild) 430 { 431 var row = prevTableBody.firstChild; 432 if (hasClass(row, "netRow") && hasClass(row, "hasHeaders") && !hasClass(row, "history")) 433 { 434 row.repObject.history = true; 435 files.push({ 436 file: row.repObject, 437 offset: 0 + "%", 438 width: 0 + "%", 439 elapsed: -1 440 }); 441 } 442 443 if (hasClass(row, "netPageRow")) 444 { 445 removeClass(row, "opened"); 446 tbody.insertBefore(row, lastRow); 447 } 448 else 449 prevTableBody.removeChild(row); 450 } 451 452 if (files.length) 453 { 454 var pageRow = NetPage.pageTag.insertRows({page: state}, lastRow)[0]; 455 pageRow.files = files; 456 457 lastRow = tbody.lastChild.previousSibling; 458 } 459 460 if (this.table.getElementsByClassName("netPageRow").item(0)) 461 NetPage.separatorTag.insertRows({}, lastRow); 462 463 scrollToBottom(this.panelNode); 464 }, 465 466 savePersistedContent: function(state) 467 { 468 Firebug.ActivablePanel.savePersistedContent.apply(this, arguments); 469 470 state.pageTitle = this.context.getTitle(); 471 }, 472 473 // UI Listener 474 showUI: function(browser, context) 475 { 476 }, 477 478 hideUI: function(browser, context) 479 { 480 }, 481 482 show: function(state) 483 { 484 if (FBTrace.DBG_NET) 485 FBTrace.sysout("net.netPanel.show; " + this.context.getName(), state); 486 487 var enabled = Firebug.NetMonitor.isAlwaysEnabled(); 488 this.showToolbarButtons("fbNetButtons", enabled); 489 490 if (enabled) 491 { 492 Firebug.NetMonitor.disabledPanelPage.hide(this); 493 Firebug.chrome.setGlobalAttribute("cmd_togglePersistNet", "checked", this.persistContent); 494 } 495 else 496 { 497 Firebug.NetMonitor.disabledPanelPage.show(this); 498 this.table = null; 499 } 500 501 if (!enabled) 502 return; 503 504 if (!this.filterCategory) 505 this.setFilter(Firebug.netFilterCategory); 506 507 this.layout(); 508 509 if (!this.layoutInterval) 510 this.layoutInterval = setInterval(bindFixed(this.updateLayout, this), layoutInterval); 511 512 if (this.wasScrolledToBottom) 513 scrollToBottom(this.panelNode); 514 }, 515 516 hide: function() 517 { 518 if (FBTrace.DBG_NET) 519 FBTrace.sysout("net.netPanel.hide; " + this.context.getName()); 520 521 this.showToolbarButtons("fbNetButtons", false); 522 523 Firebug.Debugger.syncCommands(this.context); 524 525 delete this.infoTipURL; // clear the state that is tracking the infotip so it is reset after next show() 526 this.wasScrolledToBottom = isScrolledToBottom(this.panelNode); 527 528 clearInterval(this.layoutInterval); 529 delete this.layoutInterval; 530 }, 531 532 updateOption: function(name, value) 533 { 534 if (name == "netFilterCategory") 535 { 536 Firebug.NetMonitor.syncFilterButtons(Firebug.chrome); 537 for (var i = 0; i < TabWatcher.contexts.length; ++i) 538 { 539 var context = TabWatcher.contexts[i]; 540 Firebug.NetMonitor.onToggleFilter(context, value); 541 } 542 } 543 }, 544 545 updateSelection: function(object) 546 { 547 if (!object) 548 return; 549 550 var netProgress = this.context.netProgress; 551 var file = netProgress.getRequestFile(object.request); 552 if (!file) 553 { 554 for (var i=0; i<netProgress.requests.length; i++) { 555 if (safeGetName(netProgress.requests[i]) == object.href) { 556 file = netProgress.files[i]; 557 break; 558 } 559 } 560 } 561 562 if (file) 563 { 564 scrollIntoCenterView(file.row); 565 if (!hasClass(file.row, "opened")) 566 NetRequestEntry.toggleHeadersRow(file.row); 567 } 568 }, 569 570 getPopupObject: function(target) 571 { 572 var header = getAncestorByClass(target, "netHeaderRow"); 573 if (header) 574 return NetRequestTable; 575 576 return Firebug.ActivablePanel.getPopupObject.apply(this, arguments); 577 }, 578 579 supportsObject: function(object) 580 { 581 return ((object instanceof SourceLink && object.type == "net") ? 2 : 0); 582 }, 583 584 getOptionsMenuItems: function() 585 { 586 return [ 587 this.disableCacheOption() 588 ]; 589 }, 590 591 disableCacheOption: function() 592 { 593 var BrowserCache = Firebug.NetMonitor.BrowserCache; 594 var disabled = !BrowserCache.isEnabled(); 595 return {label: "net.option.Disable Browser Cache", type: "checkbox", checked: disabled, 596 command: bindFixed(BrowserCache.enable, BrowserCache, disabled) }; 597 }, 598 599 getContextMenuItems: function(nada, target) 600 { 601 var items = []; 602 603 var file = Firebug.getRepObject(target); 604 if (!file || !(file instanceof NetFile)) 605 return items; 606 607 var object = Firebug.getObjectByURL(this.context, file.href); 608 var isPost = Utils.isURLEncodedRequest(file, this.context); 609 610 items.push( 611 {label: "CopyLocation", command: bindFixed(copyToClipboard, FBL, file.href) } 612 ); 613 614 if (isPost) 615 { 616 items.push( 617 {label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, file) } 618 ); 619 } 620 621 items.push( 622 {label: "CopyRequestHeaders", 623 command: bindFixed(this.copyHeaders, this, file.requestHeaders) }, 624 {label: "CopyResponseHeaders", 625 command: bindFixed(this.copyHeaders, this, file.responseHeaders) } 626 ); 627 628 if (textFileCategories.hasOwnProperty(file.category)) 629 { 630 items.push( 631 {label: "CopyResponse", command: bindFixed(this.copyResponse, this, file) } 632 ); 633 } 634 635 items.push( 636 "-", 637 {label: "OpenInTab", command: bindFixed(this.openRequestInTab, this, file) } 638 ); 639 640 if (textFileCategories.hasOwnProperty(file.category)) 641 { 642 items.push( 643 {label: "Open Response In New Tab", command: bindFixed(this.openResponseInTab, this, file) } 644 ); 645 } 646 647 if (!file.loaded) 648 { 649 items.push( 650 "-", 651 {label: "StopLoading", command: bindFixed(this.stopLoading, this, file) } 652 ); 653 } 654 655 if (object) 656 { 657 var subItems = Firebug.chrome.getInspectMenuItems(object); 658 if (subItems.length) 659 { 660 items.push("-"); 661 items.push.apply(items, subItems); 662 } 663 } 664 665 if (file.isXHR) 666 { 667 var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL()); 668 669 items.push( 670 "-", 671 {label: "net.label.Break On XHR", type: "checkbox", checked: !!bp, 672 command: bindFixed(this.breakOnRequest, this, file) } 673 ); 674 675 if (bp) 676 { 677 items.push( 678 {label: "EditBreakpointCondition", 679 command: bindFixed(this.editBreakpointCondition, this, file) } 680 ); 681 } 682 } 683 684 return items; 685 }, 686 687 // Context menu commands 688 copyParams: function(file) 689 { 690 var text = Utils.getPostText(file, this.context, true); 691 var url = reEncodeURL(file, text, true); 692 copyToClipboard(url); 693 }, 694 695 copyHeaders: function(headers) 696 { 697 var lines = []; 698 if (headers) 699 { 700 for (var i = 0; i < headers.length; ++i) 701 { 702 var header = headers[i]; 703 lines.push(header.name + ": " + header.value); 704 } 705 } 706 707 var text = lines.join("\r\n"); 708 copyToClipboard(text); 709 }, 710 711 copyResponse: function(file) 712 { 713 var allowDoublePost = Firebug.getPref(Firebug.prefDomain, "allowDoublePost"); 714 if (!allowDoublePost && !file.cacheEntry) 715 { 716 if (!confirm("The response can be re-requested from the server, OK?")) 717 return; 718 } 719 720 // Copy response to the clipboard 721 copyToClipboard(Utils.getResponseText(file, this.context)); 722 }, 723 724 openRequestInTab: function(file) 725 { 726 openNewTab(file.href, file.postText); 727 }, 728 729 openResponseInTab: function(file) 730 { 731 try 732 { 733 var response = Utils.getResponseText(file, this.context); 734 var inputStream = getInputStreamFromString(response); 735 var stream = CCIN("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream"); 736 stream.setInputStream(inputStream); 737 var encodedResponse = btoa(stream.readBytes(stream.available())); 738 var dataURI = "data:" + file.request.contentType + ";base64," + encodedResponse; 739 gBrowser.selectedTab = gBrowser.addTab(dataURI); 740 } 741 catch (err) 742 { 743 if (FBTrace.DBG_ERRORS) 744 FBTrace.sysout("net.openResponseInTab EXCEPTION", err); 745 } 746 }, 747 748 breakOnRequest: function(file) 749 { 750 if (!file.isXHR) 751 return; 752 753 // Create new or remove an existing breakpoint. 754 var breakpoints = this.context.netProgress.breakpoints; 755 var url = file.getFileURL(); 756 var bp = breakpoints.findBreakpoint(url); 757 if (bp) 758 breakpoints.removeBreakpoint(url); 759 else 760 breakpoints.addBreakpoint(url); 761 762 this.enumerateRequests(function(currFile) 763 { 764 if (url != currFile.getFileURL()) 765 return; 766 767 if (bp) 768 currFile.row.removeAttribute("breakpoint"); 769 else 770 currFile.row.setAttribute("breakpoint", "true"); 771 }) 772 }, 773 774 stopLoading: function(file) 775 { 776 const NS_BINDING_ABORTED = 0x804b0002; 777 778 file.request.cancel(NS_BINDING_ABORTED); 779 }, 780 781 // Support for xhr breakpoint conditions. 782 onContextMenu: function(event) 783 { 784 if (!hasClass(event.target, "sourceLine")) 785 return; 786 787 var row = getAncestorByClass(event.target, "netRow"); 788 if (!row) 789 return; 790 791 var file = row.repObject; 792 var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL()); 793 if (!bp) 794 return; 795 796 this.editBreakpointCondition(file); 797 cancelEvent(event); 798 }, 799 800 editBreakpointCondition: function(file) 801 { 802 var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL()); 803 if (!bp) 804 return; 805 806 var condition = bp ? bp.condition : ""; 807 808 this.selectedSourceBox = this.panelNode; 809 Firebug.Editor.startEditing(file.row, condition); 810 }, 811 812 getEditor: function(target, value) 813 { 814 if (!this.conditionEditor) 815 this.conditionEditor = new Firebug.NetMonitor.ConditionEditor(this.document); 816 817 return this.conditionEditor; 818 }, 819 820 // Support for activation. 821 disablePanel: function(module) 822 { 823 Firebug.ActivablePanel.disablePanel.apply(this, arguments); 824 this.table = null; 825 }, 826 827 breakOnNext: function(breaking) 828 { 829 this.context.breakOnXHR = breaking; 830 }, 831 832 shouldBreakOnNext: function() 833 { 834 return this.context.breakOnXHR; 835 }, 836 837 getBreakOnNextTooltip: function(enabled) 838 { 839 return (enabled ? $STR("net.Disable Break On XHR") : $STR("net.Break On XHR")); 840 }, 841 842 // Support for info tips. 843 showInfoTip: function(infoTip, target, x, y) 844 { 845 var row = getAncestorByClass(target, "netRow"); 846 if (row && row.repObject) 847 { 848 if (getAncestorByClass(target, "netTotalSizeCol")) 849 { 850 var infoTipURL = "netTotalSize"; 851 if (infoTipURL == this.infoTipURL) 852 return true; 853 854 this.infoTipURL = infoTipURL; 855 return this.populateTotalSizeInfoTip(infoTip, row); 856 } 857 else if (getAncestorByClass(target, "netSizeCol")) 858 { 859 var infoTipURL = row.repObject.href + "-netsize"; 860 if (infoTipURL == this.infoTipURL && row.repObject == this.infoTipFile) 861 return true; 862 863 this.infoTipURL = infoTipURL; 864 this.infoTipFile = row.repObject; 865 return this.populateSizeInfoTip(infoTip, row.repObject); 866 } 867 else if (getAncestorByClass(target, "netTimeCol")) 868 { 869 var infoTipURL = row.repObject.href + "-nettime"; 870 if (infoTipURL == this.infoTipURL && row.repObject == this.infoTipFile) 871 return true; 872 873 this.infoTipURL = infoTipURL; 874 this.infoTipFile = row.repObject; 875 return this.populateTimeInfoTip(infoTip, row.repObject); 876 } 877 else if (hasClass(row, "category-image") && 878 !getAncestorByClass(target, "netRowHeader")) 879 { 880 var infoTipURL = row.repObject.href + "-image"; 881 if (infoTipURL == this.infoTipURL) 882 return true; 883 884 this.infoTipURL = infoTipURL; 885 return Firebug.InfoTip.populateImageInfoTip(infoTip, row.repObject.href); 886 } 887 } 888 }, 889 890 populateTimeInfoTip: function(infoTip, file) 891 { 892 Firebug.NetMonitor.TimeInfoTip.render(file, infoTip); 893 return true; 894 }, 895 896 populateSizeInfoTip: function(infoTip, file) 897 { 898 Firebug.NetMonitor.SizeInfoTip.render(file, infoTip); 899 return true; 900 }, 901 902 populateTotalSizeInfoTip: function(infoTip, row) 903 { 904 var totalSizeLabel = row.getElementsByClassName("netTotalSizeLabel").item(0); 905 var file = {size: totalSizeLabel.getAttribute("totalSize")}; 906 Firebug.NetMonitor.SizeInfoTip.tag.replace({file: file}, infoTip); 907 return true; 908 }, 909 910 // Support for search within the panel. 911 getSearchOptionsMenuItems: function() 912 { 913 return [ 914 Firebug.Search.searchOptionMenu("search.Case Sensitive", "searchCaseSensitive"), 915 //Firebug.Search.searchOptionMenu("search.net.Headers", "netSearchHeaders"), 916 //Firebug.Search.searchOptionMenu("search.net.Parameters", "netSearchParameters"), 917 Firebug.Search.searchOptionMenu("search.net.Response Bodies", "netSearchResponseBody") 918 ]; 919 }, 920 921 search: function(text, reverse) 922 { 923 if (!text) 924 { 925 delete this.currentSearch; 926 return false; 927 } 928 929 var row; 930 if (this.currentSearch && text == this.currentSearch.text) 931 { 932 row = this.currentSearch.findNext(true, false, reverse, Firebug.Search.isCaseSensitive(text)); 933 } 934 else 935 { 936 this.currentSearch = new NetPanelSearch(this); 937 row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text)); 938 } 939 940 if (row) 941 { 942 var sel = this.document.defaultView.getSelection(); 943 sel.removeAllRanges(); 944 sel.addRange(this.currentSearch.range); 945 946 scrollIntoCenterView(row, this.panelNode); 947 dispatch([Firebug.A11yModel], 'onNetMatchFound', [this, text, row]); 948 return true; 949 } 950 else 951 { 952 dispatch([Firebug.A11yModel], 'onNetMatchFound', [this, text, null]); 953 return false; 954 } 955 }, 956 957 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 958 959 updateFile: function(file) 960 { 961 if (!file.invalid) 962 { 963 file.invalid = true; 964 this.queue.push(file); 965 } 966 }, 967 968 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 969 970 updateLayout: function() 971 { 972 if (!this.queue.length) 973 return; 974 975 var rightNow = now(); 976 var length = this.queue.length; 977 978 if (this.panelNode.offsetHeight) 979 this.wasScrolledToBottom = isScrolledToBottom(this.panelNode); 980 981 this.layout(); 982 983 if (this.wasScrolledToBottom) 984 scrollToBottom(this.panelNode); 985 986 if (FBTrace.DBG_NET) 987 FBTrace.sysout("net.updateLayout; Layout done, time elapsed: " + 988 formatTime(now() - rightNow) + " (" + length + ")"); 989 }, 990 991 layout: function() 992 { 993 if (!this.queue.length || !this.context.netProgress || 994 !Firebug.NetMonitor.isAlwaysEnabled()) 995 return; 996 997 this.initLayout(); 998 999 var rightNow = now(); 1000 this.updateRowData(rightNow); 1001 this.updateLogLimit(maxQueueRequests); 1002 this.updateTimeline(rightNow); 1003 this.updateSummaries(rightNow); 1004 }, 1005 1006 initLayout: function() 1007 { 1008 if (!this.table) 1009 { 1010 var limitInfo = { 1011 totalCount: 0, 1012 limitPrefsTitle: $STRF("LimitPrefsTitle", [Firebug.prefDomain+".net.logLimit"]) 1013 }; 1014 1015 this.table = NetRequestTable.tableTag.append({}, this.panelNode); 1016 this.limitRow = NetLimit.createRow(this.table.firstChild, limitInfo); 1017 this.summaryRow = NetRequestEntry.summaryTag.insertRows({}, this.table.lastChild.lastChild)[0]; 1018 1019 // Update visibility of columns according to the preferences 1020 var hiddenCols = Firebug.getPref(Firebug.prefDomain, "net.hiddenColumns"); 1021 if (hiddenCols) 1022 this.table.setAttribute("hiddenCols", hiddenCols); 1023 } 1024 }, 1025 1026 updateRowData: function(rightNow) 1027 { 1028 var queue = this.queue; 1029 this.queue = []; 1030 1031 var phase; 1032 var newFileData = []; 1033 1034 for (var i = 0; i < queue.length; ++i) 1035 { 1036 var file = queue[i]; 1037 if (!file.phase) 1038 continue; 1039 1040 file.invalid = false; 1041 1042 phase = this.calculateFileTimes(file, phase, rightNow); 1043 1044 this.updateFileRow(file, newFileData); 1045 this.invalidatePhase(phase); 1046 } 1047 1048 if (newFileData.length) 1049 { 1050 var tbody = this.table.firstChild; 1051 var lastRow = tbody.lastChild.previousSibling; 1052 this.insertRows(newFileData, lastRow); 1053 } 1054 }, 1055 1056 insertRows: function(files, lastRow) 1057 { 1058 var row = NetRequestEntry.fileTag.insertRows({files: files}, lastRow)[0]; 1059 1060 for (var i = 0; i < files.length; ++i) 1061 { 1062 var file = files[i].file; 1063 row.repObject = file; 1064 file.row = row; 1065 1066 // Make sure a breakpoint is displayed. 1067 var breakpoints = this.context.netProgress.breakpoints; 1068 if (breakpoints && breakpoints.findBreakpoint(file.getFileURL())) 1069 row.setAttribute("breakpoint", "true"); 1070 1071 // Allow customization of request entries in the list. A row is represented 1072 // by <TR> HTML element. 1073 dispatch(NetRequestTable.fbListeners, "onCreateRequestEntry", [this, row]); 1074 1075 row = row.nextSibling; 1076 } 1077 }, 1078 1079 invalidatePhase: function(phase) 1080 { 1081 if (phase && !phase.invalidPhase) 1082 { 1083 phase.invalidPhase = true; 1084 this.invalidPhases = true; 1085 } 1086 }, 1087 1088 updateFileRow: function(file, newFileData) 1089 { 1090 var row = file.row; 1091 if (!row) 1092 { 1093 newFileData.push({ 1094 file: file, 1095 offset: this.barOffset + "%", 1096 width: this.barReceivingWidth + "%", 1097 elapsed: file.loaded ? this.elapsed : -1 1098 }); 1099 } 1100 else 1101 { 1102 var sizeLabel = row.childNodes[4].firstChild; 1103 1104 var sizeText = NetRequestEntry.getSize(file); 1105 1106 // Show also total downloaded size for requests in progress. 1107 if (file.totalReceived) 1108 sizeText += " (" + formatSize(file.totalReceived) + ")"; 1109 1110 sizeLabel.firstChild.nodeValue = sizeText; 1111 1112 var methodLabel = row.childNodes[2].firstChild; 1113 methodLabel.firstChild.nodeValue = NetRequestEntry.getStatus(file); 1114 1115 var hrefLabel = row.childNodes[1].firstChild; 1116 hrefLabel.firstChild.nodeValue = NetRequestEntry.getHref(file); 1117 1118 if (file.mimeType) 1119 { 1120 // Force update category. 1121 file.category = null; 1122 for (var category in fileCategories) 1123 removeClass(row, "category-" + category); 1124 setClass(row, "category-" + Utils.getFileCategory(file)); 1125 } 1126 1127 if (file.requestHeaders) 1128 setClass(row, "hasHeaders"); 1129 1130 if (file.fromCache) 1131 setClass(row, "fromCache"); 1132 else 1133 removeClass(row, "fromCache"); 1134 1135 if (NetRequestEntry.isError(file)) 1136 setClass(row, "responseError"); 1137 else 1138 removeClass(row, "responseError"); 1139 1140 var timeLabel = row.childNodes[5].childNodes[1].lastChild.firstChild; 1141 timeLabel.innerHTML = NetRequestEntry.getElapsedTime({elapsed: this.elapsed}); 1142 1143 if (file.loaded) 1144 setClass(row, "loaded"); 1145 else 1146 removeClass(row, "loaded"); 1147 1148 if (hasClass(row, "opened")) 1149 { 1150 var netInfoBox = row.nextSibling.getElementsByClassName("netInfoBody").item(0); 1151 NetInfoBody.updateInfo(netInfoBox, file, this.context); 1152 } 1153 } 1154 }, 1155 1156 updateTimeline: function(rightNow) 1157 { 1158 var tbody = this.table.firstChild; 1159 1160 // XXXjoe Don't update rows whose phase is done and layed out already 1161 var phase; 1162 for (var row = tbody.firstChild; row; row = row.nextSibling) 1163 { 1164 var file = row.repObject; 1165 1166 // Some rows aren't associated with a file (e.g. header, sumarry). 1167 if (!file) 1168 continue; 1169 1170 if (!file.loaded) 1171 continue; 1172 1173 phase = this.calculateFileTimes(file, phase, rightNow); 1174 1175 // Get bar nodes 1176 var blockingBar = row.childNodes[5].childNodes[1].childNodes[1]; 1177 var resolvingBar = blockingBar.nextSibling; 1178 var connectingBar = resolvingBar.nextSibling; 1179 var sendingBar = connectingBar.nextSibling; 1180 var waitingBar = sendingBar.nextSibling; 1181 var contentLoadBar = waitingBar.nextSibling; 1182 var windowLoadBar = contentLoadBar.nextSibling; 1183 var receivingBar = windowLoadBar.nextSibling; 1184 1185 // All bars starts at the beginning 1186 resolvingBar.style.left = connectingBar.style.left = sendingBar.style.left = 1187 blockingBar.style.left = 1188 waitingBar.style.left = receivingBar.style.left = this.barOffset + "%"; 1189 1190 // Sets width of all bars (using style). The width is computed according to measured timing. 1191 blockingBar.style.width = this.barBlockingWidth + "%"; 1192 resolvingBar.style.width = this.barResolvingWidth + "%"; 1193 connectingBar.style.width = this.barConnectingWidth + "%"; 1194 sendingBar.style.width = this.barSendingWidth + "%"; 1195 waitingBar.style.width = this.barWaitingWidth + "%"; 1196 receivingBar.style.width = this.barReceivingWidth + "%"; 1197 1198 if (this.contentLoadBarOffset) { 1199 contentLoadBar.style.left = this.contentLoadBarOffset + "%"; 1200 contentLoadBar.style.display = "block"; 1201 this.contentLoadBarOffset = null; 1202 } 1203 1204 if (this.windowLoadBarOffset) { 1205 windowLoadBar.style.left = this.windowLoadBarOffset + "%"; 1206 windowLoadBar.style.display = "block"; 1207 this.windowLoadBarOffset = null; 1208 } 1209 } 1210 }, 1211 1212 calculateFileTimes: function(file, phase, rightNow) 1213 { 1214 var phases = this.context.netProgress.phases; 1215 1216 if (phase != file.phase) 1217 { 1218 phase = file.phase; 1219 this.phaseStartTime = phase.startTime; 1220 this.phaseEndTime = phase.endTime ? phase.endTime : rightNow; 1221 1222 // End of the first phase has to respect even the window "onload" event time, which 1223 // can occur after the last received file. This sets the extent of the timeline so, 1224 // the windowLoadBar is visible. 1225 if (phase.windowLoadTime && this.phaseEndTime < phase.windowLoadTime) 1226 this.phaseEndTime = phase.windowLoadTime; 1227 1228 this.phaseElapsed = this.phaseEndTime - phase.startTime; 1229 } 1230 1231 var elapsed = file.loaded ? file.endTime - file.startTime : 0; /*this.phaseEndTime - file.startTime*/ 1232 this.barOffset = Math.floor(((file.startTime-this.phaseStartTime)/this.phaseElapsed) * 100); 1233 1234 var blockingEnd = (file.sendingTime != file.startTime) ? file.sendingTime : file.waitingForTime; 1235 1236 this.barResolvingWidth = Math.round(((file.connectingTime - file.startTime) / this.phaseElapsed) * 100); 1237 this.barConnectingWidth = Math.round(((file.connectedTime - file.startTime) / this.phaseElapsed) * 100); 1238 this.barBlockingWidth = Math.round(((blockingEnd - file.startTime) / this.phaseElapsed) * 100); 1239 this.barSendingWidth = Math.round(((file.waitingForTime - file.startTime) / this.phaseElapsed) * 100); 1240 this.barWaitingWidth = Math.round(((file.respondedTime - file.startTime) / this.phaseElapsed) * 100); 1241 this.barReceivingWidth = Math.round((elapsed / this.phaseElapsed) * 100); 1242 1243 // Total request time doesn't include the time spent in queue. 1244 // xxxHonza: since all phases are now graphically distinguished it's easy to 1245 // see blocking requests. It's make sense to display the real total time now. 1246 this.elapsed = elapsed/* - (file.sendingTime - file.connectedTime)*/; 1247 1248 // The nspr timer doesn't have 1ms precision, so it can happen that entire 1249 // request is executed in l ms (so the total is zero). Let's display at least 1250 // one bar in such a case so the timeline is visible. 1251 if (this.elapsed <= 0) 1252 this.barReceivingWidth = "1"; 1253 1254 // Compute also offset for the contentLoadBar and windowLoadBar, which are 1255 // displayed for the first phase. 1256 if (phase.contentLoadTime) 1257 this.contentLoadBarOffset = Math.floor(((phase.contentLoadTime-this.phaseStartTime)/this.phaseElapsed) * 100); 1258 1259 if (phase.windowLoadTime) 1260 this.windowLoadBarOffset = Math.floor(((phase.windowLoadTime-this.phaseStartTime)/this.phaseElapsed) * 100); 1261 1262 return phase; 1263 }, 1264 1265 updateSummaries: function(rightNow, updateAll) 1266 { 1267 if (!this.invalidPhases && !updateAll) 1268 return; 1269 1270 this.invalidPhases = false; 1271 1272 var phases = this.context.netProgress.phases; 1273 if (!phases.length) 1274 return; 1275 1276 var fileCount = 0, totalSize = 0, cachedSize = 0, totalTime = 0; 1277 for (var i = 0; i < phases.length; ++i) 1278 { 1279 var phase = phases[i]; 1280 phase.invalidPhase = false; 1281 1282 var summary = this.summarizePhase(phase, rightNow); 1283 fileCount += summary.fileCount; 1284 totalSize += summary.totalSize; 1285 cachedSize += summary.cachedSize; 1286 totalTime += summary.totalTime 1287 } 1288 1289 var row = this.summaryRow; 1290 if (!row) 1291 return; 1292 1293 var countLabel = row.childNodes[1].firstChild; 1294 countLabel.firstChild.nodeValue = $STRP("plural.Request_Count", [fileCount]); 1295 1296 var sizeLabel = row.childNodes[4].firstChild; 1297 sizeLabel.setAttribute("totalSize", totalSize); 1298 sizeLabel.firstChild.nodeValue = NetRequestEntry.formatSize(totalSize); 1299 1300 var cacheSizeLabel = row.lastChild.firstChild.firstChild; 1301 cacheSizeLabel.setAttribute("collapsed", cachedSize == 0); 1302 cacheSizeLabel.childNodes[1].firstChild.nodeValue = 1303 NetRequestEntry.formatSize(cachedSize); 1304 1305 var timeLabel = row.lastChild.firstChild.lastChild.firstChild; 1306 var timeText = NetRequestEntry.formatTime(totalTime); 1307 var firstPhase = phases[0]; 1308 if (firstPhase.windowLoadTime) 1309 { 1310 var loadTime = firstPhase.windowLoadTime - firstPhase.startTime; 1311 // xxxHonza: localization? 1312 timeText += " (onload: " + NetRequestEntry.formatTime(loadTime) + ")"; 1313 } 1314 1315 timeLabel.innerHTML = timeText; 1316 }, 1317 1318 summarizePhase: function(phase, rightNow) 1319 { 1320 var cachedSize = 0, totalSize = 0; 1321 1322 var category = Firebug.netFilterCategory; 1323 if (category == "all") 1324 category = null; 1325 1326 var fileCount = 0; 1327 var minTime = 0, maxTime = 0; 1328 1329 for (var i=0; i<phase.files.length; i++) 1330 { 1331 var file = phase.files[i]; 1332 1333 if (!category || file.category == category) 1334 { 1335 if (file.loaded) 1336 { 1337 ++fileCount; 1338 1339 if (file.size > 0) 1340 { 1341 totalSize += file.size; 1342 if (file.fromCache) 1343 cachedSize += file.size; 1344 } 1345 1346 if (!minTime || file.startTime < minTime) 1347 minTime = file.startTime; 1348 if (file.endTime > maxTime) 1349 maxTime = file.endTime; 1350 } 1351 } 1352 } 1353 1354 var totalTime = maxTime - minTime; 1355 return {cachedSize: cachedSize, totalSize: totalSize, totalTime: totalTime, 1356 fileCount: fileCount} 1357 }, 1358 1359 updateLogLimit: function(limit) 1360 { 1361 var netProgress = this.context.netProgress; 1362 1363 if (!netProgress) // XXXjjb Honza, please check, I guess we are getting here with the context not setup 1364 { 1365 if (FBTrace.DBG_NET) 1366 FBTrace.sysout("net.updateLogLimit; NO NET CONTEXT for: " + this.context.getName()); 1367 return; 1368 } 1369 1370 // Must be positive number; 1371 limit = Math.max(0, limit); 1372 1373 var filesLength = netProgress.files.length; 1374 if (!filesLength || filesLength <= limit) 1375 return; 1376 1377 // Remove old requests. 1378 var removeCount = Math.max(0, filesLength - limit); 1379 for (var i=0; i<removeCount; i++) 1380 { 1381 var file = netProgress.files[0]; 1382 this.removeLogEntry(file); 1383 1384 // Remove the file occurrence from the queue. 1385 for (var j=0; j<this.queue.length; j++) 1386 { 1387 if (this.queue[j] == file) { 1388 this.queue.splice(j, 1); 1389 j--; 1390 } 1391 } 1392 } 1393 }, 1394 1395 removeLogEntry: function(file, noInfo) 1396 { 1397 if (!this.removeFile(file)) 1398 return; 1399 1400 if (!this.table || !this.table.firstChild) 1401 return; 1402 1403 if (file.row) 1404 { 1405 // The file is loaded and there is a row that has to be removed from the UI. 1406 var tbody = this.table.firstChild; 1407 clearDomplate(file.row); 1408 tbody.removeChild(file.row); 1409 } 1410 1411 if (noInfo || !this.limitRow) 1412 return; 1413 1414 this.limitRow.limitInfo.totalCount++; 1415 1416 NetLimit.updateCounter(this.limitRow); 1417 1418 //if (netProgress.currentPhase == file.phase) 1419 // netProgress.currentPhase = null; 1420 }, 1421 1422 removeFile: function(file) 1423 { 1424 var netProgress = this.context.netProgress; 1425 var index = netProgress.files.indexOf(file); 1426 if (index == -1) 1427 return false; 1428 1429 netProgress.files.splice(index, 1); 1430 netProgress.requests.splice(index, 1); 1431 1432 // Don't forget to remove the phase whose last file has been removed. 1433 var phase = file.phase; 1434 1435 // xxxHonza: This needs to be examined yet. Looks like the queue contains 1436 // requests from the previous page. When flushed the requestedFile isn't called 1437 // and the phase is not set. 1438 if (!phase) 1439 return true; 1440 1441 phase.removeFile(file); 1442 if (!phase.files.length) 1443 { 1444 remove(netProgress.phases, phase); 1445 1446 if (netProgress.currentPhase == phase) 1447 netProgress.currentPhase = null; 1448 } 1449 1450 return true; 1451 }, 1452 1453 insertActivationMessage: function() 1454 { 1455 if (!Firebug.NetMonitor.isAlwaysEnabled()) 1456 return; 1457 1458 // Make sure the basic structure of the table panel is there. 1459 this.initLayout(); 1460 1461 // Get the last request row before summary row. 1462 var tbody = this.table.firstChild; 1463 var lastRow = tbody.lastChild.previousSibling; 1464 1465 // Insert an activation message (if the last row isn't the message already); 1466 if (hasClass(lastRow, "netActivationRow")) 1467 return; 1468 1469 var message = NetRequestEntry.activationTag.insertRows({}, lastRow)[0]; 1470 1471 if (FBTrace.DBG_NET) 1472 FBTrace.sysout("net.insertActivationMessage; " + this.context.getName(), message); 1473 }, 1474 1475 enumerateRequests: function(fn) 1476 { 1477 if (!this.table) 1478 return; 1479 1480 var rows = this.table.getElementsByClassName("netRow"); 1481 for (var i=0; i<rows.length; i++) 1482 { 1483 var row = rows[i]; 1484 var pageRow = hasClass(row, "netPageRow"); 1485 1486 if (hasClass(row, "collapsed") && !pageRow) 1487 continue; 1488 1489 if (hasClass(row, "history")) 1490 continue; 1491 1492 // Export also history. These requests can be collpased and so not visible. 1493 if (row.files) 1494 { 1495 for (var j=0; j<row.files.length; j++) 1496 fn(row.files[j].file); 1497 } 1498 1499 var file = Firebug.getRepObject(row); 1500 if (file) 1501 fn(file); 1502 } 1503 }, 1504 1505 setFilter: function(filterCategory) 1506 { 1507 this.filterCategory = filterCategory; 1508 1509 var panelNode = this.panelNode; 1510 for (var category in fileCategories) 1511 { 1512 if (filterCategory != "all" && category != filterCategory) 1513 setClass(panelNode, "hideCategory-"+category); 1514 else 1515 removeClass(panelNode, "hideCategory-"+category); 1516 } 1517 }, 1518 1519 clear: function() 1520 { 1521 clearNode(this.panelNode); 1522 1523 this.table = null; 1524 this.summaryRow = null; 1525 this.limitRow = null; 1526 1527 this.queue = []; 1528 this.invalidPhases = false; 1529 1530 if (this.context.netProgress) 1531 this.context.netProgress.clear(); 1532 1533 if (FBTrace.DBG_NET) 1534 FBTrace.sysout("net.panel.clear; " + this.context.getName()); 1535 }, 1536 }); 1537 1538 // ************************************************************************************************ 1539 1540 /** 1541 * @domplate Represents a template that is used to render basic content of the net panel. 1542 */ 1543 Firebug.NetMonitor.NetRequestTable = domplate(Firebug.Rep, new Firebug.Listener(), 1544 { 1545 inspectable: false, 1546 1547 tableTag: 1548 1549 TABLE({"class": "netTable", cellpadding: 0, cellspacing: 0, hiddenCols: "", "role": "treegrid"}, 1550 TBODY({"class": "netTableBody", "role" : "presentation"}, 1551 TR({"class": "netHeaderRow netRow focusRow outerFocusRow", onclick: "$onClickHeader", "role": "row"}, 1552 TD({id: "netBreakpointBar", width: "1%", "class": "netHeaderCell", 1553 "role": "columnheader"}, 1554 " " 1555 ), 1556 TD({id: "netHrefCol", width: "18%", "class": "netHeaderCell alphaValue a11yFocus", 1557 "role": "columnheader"}, 1558 DIV({"class": "netHeaderCellBox", 1559 title: $STR("net.header.URL Tooltip")}, 1560 $STR("net.header.URL")) 1561 ), 1562 TD({id: "netStatusCol", width: "12%", "class": "netHeaderCell alphaValue a11yFocus", 1563 "role": "columnheader"}, 1564 DIV({"class": "netHeaderCellBox", 1565 title: $STR("net.header.Status Tooltip")}, 1566 $STR("net.header.Status")) 1567 ), 1568 TD({id: "netDomainCol", width: "12%", "class": "netHeaderCell alphaValue a11yFocus", 1569 "role": "columnheader"}, 1570 DIV({"class": "netHeaderCellBox", 1571 title: $STR("net.header.Domain Tooltip")}, 1572 $STR("net.header.Domain")) 1573 ), 1574 TD({id: "netSizeCol", width: "4%", "class": "netHeaderCell a11yFocus", 1575 "role": "columnheader"}, 1576 DIV({"class": "netHeaderCellBox", 1577 title: $STR("net.header.Size Tooltip")}, 1578 $STR("net.header.Size")) 1579 ), 1580 TD({id: "netTimeCol", width: "53%", "class": "netHeaderCell a11yFocus", 1581 "role": "columnheader"}, 1582 DIV({"class": "netHeaderCellBox", 1583 title: $STR("net.header.Timeline Tooltip")}, 1584 $STR("net.header.Timeline")) 1585 ) 1586 ) 1587 ) 1588 ), 1589 1590 onClickHeader: function(event) 1591 { 1592 if (FBTrace.DBG_NET) 1593 FBTrace.sysout("net.onClickHeader\n"); 1594 1595 // Also support enter key for sorting 1596 if (!isLeftClick(event) && !(event.type == "keypress" && event.keyCode == 13)) 1597 return; 1598 1599 var table = getAncestorByClass(event.target, "netTable"); 1600 var column = getAncestorByClass(event.target, "netHeaderCell"); 1601 this.sortColumn(table, column); 1602 }, 1603 1604 sortColumn: function(table, col, direction) 1605 { 1606 if (!col) 1607 return; 1608 1609 var numerical = !hasClass(col, "alphaValue"); 1610 1611 var colIndex = 0; 1612 for (col = col.previousSibling; col; col = col.previousSibling) 1613 ++colIndex; 1614 1615 // the first breakpoint bar column is not sortable. 1616 if (colIndex == 0) 1617 return; 1618 1619 this.sort(table, colIndex, numerical, direction); 1620 }, 1621 1622 sort: function(table, colIndex, numerical, direction) 1623 { 1624 var tbody = table.lastChild; 1625 var headerRow = tbody.firstChild; 1626 1627 // Remove class from the currently sorted column 1628 var headerSorted = getChildByClass(headerRow, "netHeaderSorted"); 1629 removeClass(headerSorted, "netHeaderSorted"); 1630 if (headerSorted) 1631 headerSorted.removeAttribute("aria-sort"); 1632 1633 // Mark new column as sorted. 1634 var header = headerRow.childNodes[colIndex]; 1635 setClass(header, "netHeaderSorted"); 1636 // If the column is already using required sort direction, bubble out. 1637 if ((direction == "desc" && header.sorted == 1) || 1638 (direction == "asc" && header.sorted == -1)) 1639 return; 1640 if (header) 1641 header.setAttribute("aria-sort", header.sorted === -1 ? "descending" : "ascending"); 1642 var colID = header.getAttribute("id"); 1643 1644 var values = []; 1645 for (var row = tbody.childNodes[1]; row; row = row.nextSibling) 1646 { 1647 if (!row.repObject) 1648 continue; 1649 1650 if (hasClass(row, "history")) 1651 continue; 1652 1653 var cell = row.childNodes[colIndex]; 1654 var value = numerical ? parseFloat(cell.textContent) : cell.textContent; 1655 1656 if (colID == "netTimeCol") 1657 value = row.repObject.startTime; 1658 else if (colID == "netSizeCol") 1659 value = row.repObject.size; 1660 1661 if (hasClass(row, "opened")) 1662 { 1663 var netInfoRow = row.nextSibling; 1664 values.push({row: row, value: value, info: netInfoRow}); 1665 row = netInfoRow; 1666 } 1667 else 1668 { 1669 values.push({row: row, value: value}); 1670 } 1671 } 1672 1673 values.sort(function(a, b) { return a.value < b.value ? -1 : 1; }); 1674 1675 if ((header.sorted && header.sorted == 1) || (!header.sorted && direction == "asc")) 1676 { 1677 removeClass(header, "sortedDescending"); 1678 setClass(header, "sortedAscending"); 1679 header.sorted = -1; 1680 1681 for (var i = 0; i < values.length; ++i) 1682 { 1683 tbody.appendChild(values[i].row); 1684 if (values[i].info) 1685 tbody.appendChild(values[i].info); 1686 } 1687 } 1688 else 1689 { 1690 removeClass(header, "sortedAscending"); 1691 setClass(header, "sortedDescending"); 1692 1693 header.sorted = 1; 1694 1695 for (var i = values.length-1; i >= 0; --i) 1696 { 1697 tbody.appendChild(values[i].row); 1698 if (values[i].info) 1699 tbody.appendChild(values[i].info); 1700 } 1701 } 1702 1703 // Make sure the summary row is again at the end. 1704 var summaryRow = tbody.getElementsByClassName("netSummaryRow").item(0); 1705 tbody.appendChild(summaryRow); 1706 }, 1707 1708 supportsObject: function(object) 1709 { 1710 return (object == this); 1711 }, 1712 1713 /** 1714 * Provides menu items for header context menu. 1715 */ 1716 getContextMenuItems: function(object, target, context) 1717 { 1718 var popup = $("fbContextMenu"); 1719 if (popup.firstChild && popup.firstChild.getAttribute("command") == "cmd_copy") 1720 popup.removeChild(popup.firstChild); 1721 1722 var items = []; 1723 1724 // Iterate over all columns and create a menu item for each. 1725 var table = context.getPanel(panelName, true).table; 1726 var hiddenCols = table.getAttribute("hiddenCols"); 1727 1728 var lastVisibleIndex; 1729 var visibleColCount = 0; 1730 1731 // Iterate all columns except of the first one for breakpoints. 1732 var header = getAncestorByClass(target, "netHeaderRow"); 1733 var columns = cloneArray(header.childNodes); 1734 columns.shift(); 1735 for (var i=0; i<columns.length; i++) 1736 { 1737 var column = columns[i]; 1738 var visible = (hiddenCols.indexOf(column.id) == -1); 1739 1740 items.push({ 1741 label: column.textContent, 1742 type: "checkbox", 1743 checked: visible, 1744 nol10n: true, 1745 command: bindFixed(this.onShowColumn, this, context, column.id) 1746 }); 1747 1748 if (visible) 1749 { 1750 lastVisibleIndex = i; 1751 visibleColCount++; 1752 } 1753 } 1754 1755 // If the last column is visible, disable its menu item. 1756 if (visibleColCount == 1) 1757 items[lastVisibleIndex].disabled = true; 1758 1759 items.push("-"); 1760 items.push({ 1761 label: $STR("net.header.Reset_Header"), 1762 nol10n: true, 1763 command: bindFixed(this.onResetColumns, this, context) 1764 }); 1765 1766 return items; 1767 }, 1768 1769 onShowColumn: function(context, colId) 1770 { 1771 var table = context.getPanel(panelName, true).table; 1772 var hiddenCols = table.getAttribute("hiddenCols"); 1773 1774 // If the column is already presented in the list of hidden columns, 1775 // remove it, otherwise append. 1776 var index = hiddenCols.indexOf(colId); 1777 if (index >= 0) 1778 { 1779 table.setAttribute("hiddenCols", hiddenCols.substr(0,index-1) + 1780 hiddenCols.substr(index+colId.length)); 1781 } 1782 else 1783 { 1784 table.setAttribute("hiddenCols", hiddenCols + " " + colId); 1785 } 1786 1787 // Store current state into the preferences. 1788 Firebug.setPref(Firebug.prefDomain, "net.hiddenColumns", table.getAttribute("hiddenCols")); 1789 }, 1790 1791 onResetColumns: function(context) 1792 { 1793 var panel = context.getPanel(panelName, true); 1794 var header = panel.panelNode.getElementsByClassName("netHeaderRow").item(0); 1795 1796 // Reset widths 1797 var columns = header.childNodes; 1798 for (var i=0; i<columns.length; i++) 1799 { 1800 var col = columns[i]; 1801 if (col.style) 1802 col.style.width = ""; 1803 } 1804 1805 // Reset visibility. Only the Status column is hidden by default. 1806 panel.table.setAttribute("hiddenCols", "colStatus"); 1807 Firebug.setPref(Firebug.prefDomain, "net.hiddenColumns", "colStatus"); 1808 }, 1809 }); 1810 1811 var NetRequestTable = Firebug.NetMonitor.NetRequestTable; 1812 1813 // ************************************************************************************************ 1814 1815 /** 1816 * @domplate Represents a template that is used to render net panel entries. 1817 */ 1818 Firebug.NetMonitor.NetRequestEntry = domplate(Firebug.Rep, new Firebug.Listener(), 1819 { 1820 fileTag: 1821 FOR("file", "$files", 1822 TR({"class": "netRow $file.file|getCategory focusRow outerFocusRow", 1823 onclick: "$onClick", "role": "row", "aria-expanded": "false", 1824 $hasHeaders: "$file.file|hasRequestHeaders", 1825 $history: "$file.file.history", 1826 $loaded: "$file.file.loaded", 1827 $responseError: "$file.file|isError", 1828 $fromCache: "$file.file.fromCache", 1829 $inFrame: "$file.file|getInFrame"}, 1830 TD({"class": "netCol"}, 1831 DIV({"class": "sourceLine netRowHeader", 1832 onclick: "$onClickRowHeader"}, 1833 " " 1834 ) 1835 ), 1836 TD({"class": "netHrefCol netCol a11yFocus", "role": "rowheader"}, 1837 DIV({"class": "netHrefLabel netLabel", 1838 style: "margin-left: $file.file|getIndent\\px"}, 1839 "$file.file|getHref" 1840 ), 1841 DIV({"class": "netFullHrefLabel netHrefLabel", 1842 style: "margin-left: $file.file|getIndent\\px"}, 1843 "$file.file.href" 1844 ) 1845 ), 1846 TD({"class": "netStatusCol netCol a11yFocus", "role": "gridcell"}, 1847 DIV({"class": "netStatusLabel netLabel"}, "$file.file|getStatus") 1848 ), 1849 TD({"class": "netDomainCol netCol a11yFocus", "role": "gridcell" }, 1850 DIV({"class": "netDomainLabel netLabel"}, "$file.file|getDomain") 1851 ), 1852 TD({"class": "netSizeCol netCol a11yFocus", "role": "gridcell", "aria-describedby": "fbNetSizeInfoTip"}, 1853 DIV({"class": "netSizeLabel netLabel"}, "$file.file|getSize") 1854 ), 1855 TD({"class": "netTimeCol netCol a11yFocus", "role": "gridcell", "aria-describedby": "fbNetTimeInfoTip" }, 1856 DIV({"class": "netLoadingIcon"}), 1857 DIV({"class": "netBar"}, 1858 " ", 1859 DIV({"class": "netBlockingBar", style: "left: $file.offset"}), 1860 DIV({"class": "netResolvingBar", style: "left: $file.offset"}), 1861 DIV({"class": "netConnectingBar", style: "left: $file.offset"}), 1862 DIV({"class": "netSendingBar", style: "left: $file.offset"}), 1863 DIV({"class": "netWaitingBar", style: "left: $file.offset"}), 1864 DIV({"class": "netContentLoadBar", style: "left: $file.offset"}), 1865 DIV({"class": "netWindowLoadBar", style: "left: $file.offset"}), 1866 DIV({"class": "netReceivingBar", style: "left: $file.offset; width: $file.width"}, 1867 SPAN({"class": "netTimeLabel"}, "$file|getElapsedTime") 1868 ) 1869 ) 1870 ) 1871 ) 1872 ), 1873 1874 headTag: 1875 TR({"class": "netHeadRow"}, 1876 TD({"class": "netHeadCol", colspan: 6}, 1877 DIV({"class": "netHeadLabel"}, "$doc.rootFile.href") 1878 ) 1879 ), 1880 1881 netInfoTag: 1882 TR({"class": "netInfoRow outerFocusRow", "role" : "row"}, 1883 TD({"class": "sourceLine netRowHeader"}), 1884 TD({"class": "netInfoCol", colspan: 5, "role" : "gridcell"}) 1885 ), 1886 1887 activationTag: 1888 TR({"class": "netRow netActivationRow"}, 1889 TD({"class": "netCol netActivationLabel", colspan: 6, "role": "status"}, 1890 $STR("net.ActivationMessage") 1891 ) 1892 ), 1893 1894 summaryTag: 1895 TR({"class": "netRow netSummaryRow focusRow outerFocusRow", "role": "row", "aria-live": "polite"}, 1896 TD({"class": "netCol"}, " "), 1897 TD({"class": "netCol netHrefCol a11yFocus", "role" : "rowheader"}, 1898 DIV({"class": "netCountLabel netSummaryLabel"}, "-") 1899 ), 1900 TD({"class": "netCol netStatusCol a11yFocus", "role" : "gridcell"}), 1901 TD({"class": "netCol netDomainCol a11yFocus", "role" : "gridcell"}), 1902 TD({"class": "netTotalSizeCol netCol netSizeCol a11yFocus", "role" : "gridcell"}, 1903 DIV({"class": "netTotalSizeLabel netSummaryLabel"}, "0KB") 1904 ), 1905 TD({"class": "netTotalTimeCol netCol netTimeCol a11yFocus", "role" : "gridcell"}, 1906 DIV({"class": "netSummaryBar", style: "width: 100%"}, 1907 DIV({"class": "netCacheSizeLabel netSummaryLabel", collapsed: "true"}, 1908 "(", 1909 SPAN("0KB"), 1910 SPAN(" " + $STR("FromCache")), 1911 ")" 1912 ), 1913 DIV({"class": "netTimeBar"}, 1914 SPAN({"class": "netTotalTimeLabel netSummaryLabel"}, "0ms") 1915 ) 1916 ) 1917 ) 1918 ), 1919 1920 onClickRowHeader: function(event) 1921 { 1922 cancelEvent(event); 1923 1924 var rowHeader = event.target; 1925 if (!hasClass(rowHeader, "netRowHeader")) 1926 return; 1927 1928 var row = getAncestorByClass(event.target, "netRow"); 1929 if (!row) 1930 return; 1931 1932 var context = Firebug.getElementPanel(row).context; 1933 var panel = context.getPanel(panelName, true); 1934 if (panel) 1935 panel.breakOnRequest(row.repObject); 1936 }, 1937 1938 onClick: function(event) 1939 { 1940 if (isLeftClick(event)) 1941 { 1942 var row = getAncestorByClass(event.target, "netRow"); 1943 if (row) 1944 { 1945 // Click on the rowHeader element inserts a breakpoint. 1946 if (getAncestorByClass(event.target, "netRowHeader")) 1947 return; 1948 1949 this.toggleHeadersRow(row); 1950 cancelEvent(event); 1951 } 1952 } 1953 }, 1954 1955 toggleHeadersRow: function(row) 1956 { 1957 if (!hasClass(row, "hasHeaders")) 1958 return; 1959 1960 var file = row.repObject; 1961 1962 toggleClass(row, "opened"); 1963 if (hasClass(row, "opened")) 1964 { 1965 var netInfoRow = this.netInfoTag.insertRows({}, row)[0]; 1966 var netInfoCol = netInfoRow.getElementsByClassName("netInfoCol").item(0); 1967 var netInfoBox = NetInfoBody.tag.replace({file: file}, netInfoCol); 1968 1969 // Notify listeners so additional tabs can be created. 1970 dispatch(NetInfoBody.fbListeners, "initTabBody", [netInfoBox, file]); 1971 1972 NetInfoBody.selectTabByName(netInfoBox, "Headers"); 1973 var category = Utils.getFileCategory(row.repObject); 1974 if (category) 1975 setClass(netInfoBox, "category-" + category); 1976 row.setAttribute('aria-expanded', 'true'); 1977 } 1978 else 1979 { 1980 var netInfoRow = row.nextSibling; 1981 var netInfoBox = netInfoRow.getElementsByClassName("netInfoBody").item(0); 1982 1983 dispatch(NetInfoBody.fbListeners, "destroyTabBody", [netInfoBox, file]); 1984 1985 row.parentNode.removeChild(netInfoRow); 1986 row.setAttribute('aria-expanded', 'false'); 1987 } 1988 }, 1989 1990 getCategory: function(file) 1991 { 1992 var category = Utils.getFileCategory(file); 1993 if (category) 1994 return "category-" + category; 1995 1996 return "category-undefined"; 1997 }, 1998 1999 getInFrame: function(file) 2000 { 2001 return !!file.document.parent; 2002 }, 2003 2004 getIndent: function(file) 2005 { 2006 // XXXjoe Turn off indenting for now, it's confusing since we don't 2007 // actually place nested files directly below their parent 2008 //return file.document.level * indentWidth; 2009 return 10; 2010 }, 2011 2012 isError: function(file) 2013 { 2014 if (file.aborted) 2015 return true; 2016 2017 var errorRange = Math.floor(file.responseStatus/100); 2018 return errorRange == 4 || errorRange == 5; 2019 }, 2020 2021 getHref: function(file) 2022 { 2023 return (file.method ? file.method.toUpperCase() : "?") + " " + getFileName(file.href); 2024 }, 2025 2026 getStatus: function(file) 2027 { 2028 var text = ""; 2029 2030 if (file.responseStatus) 2031 text += file.responseStatus + " "; 2032 2033 if (file.responseStatusText) 2034 text += file.responseStatusText; 2035 2036 return text ? text : " "; 2037 }, 2038 2039 getDomain: function(file) 2040 { 2041 return getPrettyDomain(file.href); 2042 }, 2043 2044 getSize: function(file) 2045 { 2046 return this.formatSize(file.size); 2047 }, 2048 2049 getElapsedTime: function(file) 2050 { 2051 if (!file.elapsed || file.elapsed < 0) 2052 return ""; 2053 2054 return this.formatTime(file.elapsed); 2055 }, 2056 2057 hasRequestHeaders: function(file) 2058 { 2059 return !!file.requestHeaders; 2060 }, 2061 2062 formatSize: function(bytes) 2063 { 2064 return formatSize(bytes); 2065 }, 2066 2067 formatTime: function(elapsed) 2068 { 2069 // Use formatTime util from the lib. 2070 return formatTime(elapsed); 2071 } 2072 }); 2073 2074 var NetRequestEntry = Firebug.NetMonitor.NetRequestEntry; 2075 2076 // ************************************************************************************************ 2077 2078 Firebug.NetMonitor.NetPage = domplate(Firebug.Rep, 2079 { 2080 separatorTag: 2081 TR({"class": "netRow netPageSeparatorRow"}, 2082 TD({"class": "netCol netPageSeparatorLabel", colspan: 6, "role": "separator"}) 2083 ), 2084 2085 pageTag: 2086 TR({"class": "netRow netPageRow", onclick: "$onPageClick"}, 2087 TD({"class": "netCol netPageCol", colspan: 6, "role": "separator"}, 2088 DIV({"class": "netLabel netPageLabel netPageTitle"}, "$page|getTitle") 2089 ) 2090 ), 2091 2092 getTitle: function(page) 2093 { 2094 return page.pageTitle; 2095 }, 2096 2097 onPageClick: function(event) 2098 { 2099 if (!isLeftClick(event)) 2100 return; 2101 2102 var target = event.target; 2103 var pageRow = getAncestorByClass(event.target, "netPageRow"); 2104 var panel = Firebug.getElementPanel(pageRow); 2105 2106 if (!hasClass(pageRow, "opened")) 2107 { 2108 setClass(pageRow, "opened"); 2109 2110 var files = pageRow.files; 2111 2112 // Move all net-rows from the persistedState to this panel. 2113 panel.insertRows(files, pageRow); 2114 2115 for (var i=0; i<files.length; i++) 2116 panel.queue.push(files[i].file); 2117 2118 panel.layout(); 2119 } 2120 else 2121 { 2122 removeClass(pageRow, "opened"); 2123 2124 var nextRow = pageRow.nextSibling; 2125 while (!hasClass(nextRow, "netPageRow") && !hasClass(nextRow, "netPageSeparatorRow")) 2126 { 2127 var nextSibling = nextRow.nextSibling; 2128 nextRow.parentNode.removeChild(nextRow); 2129 nextRow = nextSibling; 2130 } 2131 } 2132 }, 2133 }); 2134 2135 var NetPage = Firebug.NetMonitor.NetPage; 2136 2137 // ************************************************************************************************ 2138 2139 /** 2140 * @domplate Represents a template that is used to reneder detailed info about a request. 2141 * This template is rendered when a request is expanded. 2142 */ 2143 Firebug.NetMonitor.NetInfoBody = domplate(Firebug.Rep, new Firebug.Listener(), 2144 { 2145 tag: 2146 DIV({"class": "netInfoBody", _repObject: "$file"}, 2147 TAG("$infoTabs", {file: "$file"}), 2148 TAG("$infoBodies", {file: "$file"}) 2149 ), 2150 2151 infoTabs: 2152 DIV({"class": "netInfoTabs focusRow subFocusRow", "role": "tablist"}, 2153 A({"class": "netInfoParamsTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", 2154 view: "Params", 2155 $collapsed: "$file|hideParams"}, 2156 $STR("URLParameters") 2157 ), 2158 A({"class": "netInfoHeadersTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", 2159 view: "Headers"}, 2160 $STR("Headers") 2161 ), 2162 A({"class": "netInfoPostTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", 2163 view: "Post", 2164 $collapsed: "$file|hidePost"}, 2165 $STR("Post") 2166 ), 2167 A({"class": "netInfoPutTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", 2168 view: "Put", 2169 $collapsed: "$file|hidePut"}, 2170 $STR("Put") 2171 ), 2172 A({"class": "netInfoResponseTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", 2173 view: "Response", 2174 $collapsed: "$file|hideResponse"}, 2175 $STR("Response") 2176 ), 2177 A({"