1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/lib/locale", 7 "firebug/lib/events", 8 "firebug/lib/url", 9 "firebug/js/sourceLink", 10 "firebug/lib/http", 11 "firebug/lib/css", 12 "firebug/chrome/window", 13 "firebug/lib/string", 14 "firebug/lib/array", 15 "firebug/lib/system", 16 "firebug/net/netUtils", 17 "firebug/lib/xpcom" 18 ], 19 function(Obj, Firebug, Locale, Events, Url, SourceLink, Http, Css, Win, Str, 20 Arr, System, NetUtils, Xpcom) { 21 22 // ********************************************************************************************* // 23 // Constants 24 25 const Cc = Components.classes; 26 const Ci = Components.interfaces; 27 const Cr = Components.results; 28 29 const CacheService = Cc["@mozilla.org/network/cache-service;1"]; 30 31 var versionChecker = Xpcom.CCSV("@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator"); 32 var appInfo = Xpcom.CCSV("@mozilla.org/xre/app-info;1", "nsIXULAppInfo"); 33 var fx18 = versionChecker.compare(appInfo.version, "18") >= 0; 34 35 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 36 37 const reIgnore = /about:|javascript:|resource:|chrome:|jar:/; 38 const reResponseStatus = /HTTP\/1\.\d\s(\d+)\s(.*)/; 39 40 var cacheSession = null; 41 var panelName = "net"; 42 43 // ********************************************************************************************* // 44 // Net Progress 45 46 function NetProgress(context) 47 { 48 if (FBTrace.DBG_NET) 49 FBTrace.sysout("net.NetProgress.constructor; " + 50 (context ? context.getName() : "NULL Context")); 51 52 this.context = context; 53 54 var panel = null; 55 var queue = []; 56 57 this.post = function(handler, args) 58 { 59 if (panel) 60 { 61 var file = handler.apply(this, args); 62 if (file) 63 { 64 panel.updateFile(file); 65 66 // If the panel isn't currently visible, make sure the limit is up to date. 67 if (!panel.layoutInterval) 68 panel.updateLogLimit(Firebug.NetMonitor.maxQueueRequests); 69 70 return file; 71 } 72 } 73 else 74 { 75 // The first page request is made before the initContext (known problem). 76 queue.push(handler, args); 77 } 78 }; 79 80 this.flush = function() 81 { 82 for (var i=0; i<queue.length; i+=2) 83 this.post(queue[i], queue[i+1]); 84 85 queue = []; 86 }; 87 88 this.activate = function(activePanel) 89 { 90 this.panel = panel = activePanel; 91 if (panel) 92 this.flush(); 93 }; 94 95 this.update = function(file) 96 { 97 if (panel) 98 panel.updateFile(file); 99 }; 100 101 this.clear = function() 102 { 103 for (var i=0; this.files && i<this.files.length; i++) 104 this.files[i].clear(); 105 106 this.requests = []; 107 this.files = []; 108 this.phases = []; 109 this.documents = []; 110 this.windows = []; 111 this.currentPhase = null; 112 113 queue = []; 114 }; 115 116 this.clear(); 117 } 118 119 NetProgress.prototype = 120 { 121 dispatchName: "netProgress", 122 panel: null, 123 124 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 125 126 requestNumber: 1, 127 128 openingFile: function openingFile(request, win) 129 { 130 if (!fx18) 131 return; 132 133 var file = this.getRequestFile(request, win); 134 if (file) 135 { 136 // Parse URL params so, they are available for conditional breakpoints. 137 file.urlParams = Url.parseURLParams(file.href); 138 this.breakOnXHR(file); 139 } 140 }, 141 142 startFile: function startFile(request, win) 143 { 144 // Called asynchronously since Fx17 so, can't be use for Break on XHR 145 // since JS stack is not available at the moment. 146 // See https://bugzilla.mozilla.org/show_bug.cgi?id=800799 147 if (fx18) 148 return; 149 150 var file = this.getRequestFile(request, win); 151 if (file) 152 { 153 // Parse URL params so, they are available for conditional breakpoints. 154 file.urlParams = Url.parseURLParams(file.href); 155 this.breakOnXHR(file); 156 } 157 }, 158 159 requestedHeaderFile: function requestedHeaderFile(request, time, win, xhr, extraStringData) 160 { 161 var file = this.getRequestFile(request); 162 if (file) 163 { 164 logTime(file, "requestedHeaderFile", time); 165 166 file.requestHeadersText = extraStringData; 167 168 this.requestedFile(request, time, win, xhr); 169 170 Events.dispatch(Firebug.NetMonitor.fbListeners, "onRequest", [this.context, file]); 171 } 172 }, 173 174 // Can be called from onModifyRequest (to catch request start even in case of BF cache) and also 175 // from requestHeaderFile (activity observer) 176 requestedFile: function requestedFile(request, time, win, xhr) 177 { 178 var file = this.getRequestFile(request, win); 179 if (file) 180 { 181 logTime(file, "requestedFile", time); 182 183 if (FBTrace.DBG_NET_EVENTS) 184 FBTrace.sysout("net.events.requestedFile +0 " + getPrintableTime() + ", " + 185 request.URI.path, file); 186 187 // For cached image files, we may never hear another peep from any observers 188 // after this point, so we have to assume that the file is cached and loaded 189 // until we get a respondedFile call later 190 file.startTime = file.endTime = time; 191 file.resolvingTime = time; 192 file.connectingTime = time; 193 file.connectedTime = time; 194 file.sendingTime = time; 195 file.waitingForTime = time; 196 file.respondedTime = time; 197 file.isXHR = xhr; 198 file.isBackground = request.loadFlags & Ci.nsIRequest.LOAD_BACKGROUND; 199 file.method = request.requestMethod; 200 201 if (!Ci.nsIHttpActivityDistributor) 202 NetUtils.getPostText(file, this.context); 203 204 this.extendPhase(file); 205 206 return file; 207 } 208 else 209 { 210 if (FBTrace.DBG_NET) 211 FBTrace.sysout("net.requestedFile no file for request="); 212 } 213 }, 214 215 breakOnXHR: function breakOnXHR(file) 216 { 217 var halt = false; 218 var conditionIsFalse = false; 219 220 // If there is an enabled breakpoint with condition: 221 // 1) break if the condition is evaluated to true. 222 var breakpoints = this.context.netProgress.breakpoints; 223 var bp = breakpoints ? breakpoints.findBreakpoint(file.getFileURL()) : null; 224 if (bp && bp.checked) 225 { 226 halt = true; 227 if (bp.condition) 228 { 229 halt = bp.evaluateCondition(this.context, file); 230 conditionIsFalse = !halt; 231 } 232 } 233 234 // 2) If break on XHR flag is set and there is no condition evaluated to false, 235 // break with "break on next" breaking cause (this new breaking cause can override 236 // an existing one that is set when evaluating a breakpoint condition). 237 if (this.context.breakOnXHR && !conditionIsFalse) 238 { 239 this.context.breakingCause = { 240 title: Locale.$STR("net.Break On XHR"), 241 message: Str.cropString(file.href, 200), 242 copyAction: Obj.bindFixed(System.copyToClipboard, System, file.href) 243 }; 244 245 halt = true; 246 } 247 248 // Ignore if there is no reason to break. 249 if (!halt) 250 return; 251 252 // Even if the execution was stopped at breakpoint reset the global 253 // breakOnXHR flag. 254 this.context.breakOnXHR = false; 255 256 Firebug.Breakpoint.breakNow(this.context.getPanel(panelName, true)); 257 }, 258 259 respondedHeaderFile: function respondedHeaderFile(request, time, extraStringData) 260 { 261 var file = this.getRequestFile(request); 262 if (file) 263 { 264 logTime(file, "respondedHeaderFile", time); 265 266 file.responseHeadersText = extraStringData; 267 } 268 }, 269 270 bodySentFile: function bodySentFile(request, time) 271 { 272 var file = this.getRequestFile(request); 273 if (file) 274 { 275 logTime(file, "bodySentFile", time); 276 277 NetUtils.getPostText(file, this.context); 278 } 279 }, 280 281 responseStartedFile: function responseStartedFile(request, time) 282 { 283 var file = this.getRequestFile(request); 284 if (file) 285 { 286 logTime(file, "responseStartedFile", time); 287 288 if (!file.responseStarted) 289 { 290 file.respondedTime = time; 291 file.responseStarted = true; 292 } 293 294 file.endTime = time; 295 return file; 296 } 297 }, 298 299 respondedFile: function respondedFile(request, time, info) 300 { 301 Events.dispatch(Firebug.NetMonitor.fbListeners, "onExamineResponse", [this.context, request]); 302 303 var file = this.getRequestFile(request); 304 if (file) 305 { 306 logTime(file, "respondedFile", time); 307 308 if (!Ci.nsIHttpActivityDistributor) 309 { 310 file.respondedTime = time; 311 file.endTime = time; 312 313 if (request.contentLength >= 0) 314 file.size = request.contentLength; 315 } 316 317 if (info) 318 { 319 if (info.responseStatus == 304) 320 file.fromCache = true; 321 else if (!file.fromCache) 322 file.fromCache = false; 323 } 324 325 // respondedFile can be executed asynchronously and getting headers now 326 // could be too late. They could be already replaced by cached headers. 327 if (info.responseHeaders) 328 file.responseHeaders = info.responseHeaders; 329 330 // Get also request headers (and perhaps also responseHeaders, they won't be 331 // replaced if already available). 332 NetUtils.getHttpHeaders(request, file, this.context); 333 334 if (info) 335 { 336 file.responseStatus = info.responseStatus; 337 file.responseStatusText = info.responseStatusText; 338 file.postText = info.postText; 339 } 340 341 file.aborted = false; 342 343 // Use ACTIVITY_SUBTYPE_RESPONSE_COMPLETE to get the info if possible. 344 if (!Ci.nsIHttpActivityDistributor) 345 { 346 if (file.fromCache) 347 getCacheEntry(file, this); 348 } 349 350 if (FBTrace.DBG_NET_EVENTS) 351 FBTrace.sysout("net.events.respondedFile +" + (NetUtils.now() - file.startTime) + " " + 352 getPrintableTime() + ", " + request.URI.path, file); 353 354 // The ACTIVITY_SUBTYPE_TRANSACTION_CLOSE could come earlier. 355 if (file.loaded) 356 return; 357 358 this.endLoad(file); 359 360 // If there is a network error, log it into the Console panel. 361 if (Firebug.showNetworkErrors && Firebug.NetMonitor.NetRequestEntry.isError(file)) 362 { 363 Firebug.Errors.increaseCount(this.context); 364 var message = "NetworkError: " + Firebug.NetMonitor.NetRequestEntry.getStatus(file) + " - "+file.href; 365 Firebug.Console.log(message, this.context, "error", null, true, file.getFileLink(message)); 366 } 367 368 Events.dispatch(Firebug.NetMonitor.fbListeners, "onResponse", [this.context, file]); 369 return file; 370 } 371 }, 372 373 respondedCacheFile: function respondedCacheFile(request, time, info) 374 { 375 Events.dispatch(Firebug.NetMonitor.fbListeners, "onExamineCachedResponse", 376 [this.context, request]); 377 378 var file = this.getRequestFile(request, null, true); 379 if (file) 380 { 381 logTime(file, "respondedCacheFile", time); 382 383 if (FBTrace.DBG_NET_EVENTS) 384 FBTrace.sysout("net.events.respondedCacheFile +" + (NetUtils.now() - file.startTime) + " " + 385 getPrintableTime() + ", " + request.URI.path, file); 386 387 // on-examine-cache-response is using different timer, do not track response 388 // times from the cache and use the proper waiting time. 389 if (file.waitingStarted) 390 time = file.waitingForTime; 391 392 if (!file.responseStarted) 393 { 394 file.respondedTime = time; 395 file.responseStarted = true; 396 } 397 398 file.endTime = time; 399 file.fromBFCache = true; 400 file.fromCache = true; 401 file.aborted = false; 402 403 try 404 { 405 if (request instanceof Ci.nsIApplicationCacheChannel) 406 { 407 if (request.loadedFromApplicationCache) 408 file.fromAppCache = true; 409 } 410 } 411 catch (e) 412 { 413 if (FBTrace.DBG_ERRORS) 414 FBTrace.sysout("net.respondedCacheFile ERROR " + e, e); 415 } 416 417 if (request.contentLength >= 0) 418 file.size = request.contentLength; 419 420 NetUtils.getHttpHeaders(request, file, this.context); 421 422 if (info) 423 { 424 file.responseStatus = info.responseStatus; 425 file.responseStatusText = info.responseStatusText; 426 file.postText = info.postText; 427 } 428 429 getCacheEntry(file, this); 430 431 this.endLoad(file); 432 433 Events.dispatch(Firebug.NetMonitor.fbListeners, "onCachedResponse", 434 [this.context, file]); 435 436 return file; 437 } 438 else 439 { 440 if (FBTrace.DBG_NET) 441 FBTrace.sysout("net.respondedCacheFile; NO FILE FOR " + 442 Http.safeGetRequestName(request)); 443 } 444 }, 445 446 waitingForFile: function waitingForFile(request, time) 447 { 448 var file = this.getRequestFile(request, null, true); 449 if (file) 450 { 451 logTime(file, "waitingForFile", time); 452 453 if (!file.waitingStarted) 454 { 455 file.waitingForTime = time; 456 file.waitingStarted = true; 457 } 458 } 459 460 // Don't update the UI now (optimization). 461 return null; 462 }, 463 464 sendingFile: function sendingFile(request, time, size) 465 { 466 var file = this.getRequestFile(request, null, true); 467 if (file) 468 { 469 logTime(file, "sendingFile", time); 470 471 // Remember when the send started. 472 if (!file.sendStarted) 473 { 474 file.sendingTime = time; 475 file.waitingForTime = time; // in case waiting-for would never came. 476 file.sendStarted = true; 477 } 478 479 // Catch 2. 480 // It can happen that "connected" event sometimes comes after sending, 481 // which doesn't make much sense (Firefox bug?) 482 if (!file.connected) 483 { 484 file.connected = true; 485 file.connectedTime = time; 486 } 487 488 file.totalSent = size; 489 490 // Catch 1. 491 // Request is sending so reset following flags. There are cases where 492 // RESPONSE_COMPLETE and TRANSACTION_CLOSE came in the middle of 493 // connetion initialization (resolving, connecting, connected). 494 file.loaded = false; 495 file.responseStarted = false; 496 497 if (FBTrace.DBG_NET_EVENTS) 498 FBTrace.sysout("net.events.sendingFile +" + (NetUtils.now() - file.startTime) + " " + 499 getPrintableTime() + ", " + request.URI.path, file); 500 } 501 502 // Don't update the UI now (optimization). 503 return null; 504 }, 505 506 connectingFile: function connectingFile(request, time) 507 { 508 var file = this.getRequestFile(request, null, true); 509 510 logTime(file, "connectingFile", time); 511 512 // Resolving, connecting and connected can come after the file is loaded 513 // (closedFile received). This happens if the response is coming from the 514 // cache. Just ignore it. 515 if (file && file.loaded) 516 return null; 517 518 if (file && !file.connectStarted) 519 { 520 file.connectStarted = true; 521 file.connectingTime = time; 522 file.connectedTime = time; // in case connected-to would never came. 523 file.sendingTime = time; // in case sending-to would never came. 524 file.waitingForTime = time; // in case waiting-for would never came. 525 } 526 527 // Don't update the UI now (optimization). 528 return null; 529 }, 530 531 connectedFile: function connectedFile(request, time) 532 { 533 var file = this.getRequestFile(request, null, true); 534 535 logTime(file, "connectedFile", time); 536 537 if (file && file.loaded) 538 return null; 539 540 if (file && !file.connected) 541 { 542 file.connected = true; 543 file.connectedTime = time; 544 file.sendingTime = time; // in case sending-to would never came. 545 file.waitingForTime = time; // in case waiting-for would never came. 546 } 547 548 // Don't update the UI now (optimization). 549 return null; 550 }, 551 552 receivingFile: function receivingFile(request, time, size) 553 { 554 var file = this.getRequestFile(request, null, true); 555 if (file) 556 { 557 logTime(file, "receivingFile", time); 558 559 if (FBTrace.DBG_NET_EVENTS) 560 FBTrace.sysout("net.events.receivingFile +" + time + " " + 561 getPrintableTime() + ", " + 562 Str.formatSize(size) + " (" + size + "B), " + 563 request.URI.path, file); 564 565 file.endTime = time; 566 file.totalReceived = size; 567 568 // Update phase's lastFinishedFile in case of long time downloads. 569 // This forces the timeline to have proper extent. 570 if (file.phase && file.phase.endTime < time) 571 file.phase.lastFinishedFile = file; 572 573 // Force update UI. 574 if (file.row && Css.hasClass(file.row, "opened")) 575 { 576 var netInfoBox = file.row.nextSibling.getElementsByClassName("netInfoBody").item(0); 577 if (netInfoBox) 578 { 579 netInfoBox.responsePresented = false; 580 netInfoBox.htmlPresented = false; 581 } 582 } 583 } 584 585 return file; 586 }, 587 588 responseCompletedFile: function responseCompletedFile(request, time, responseSize) 589 { 590 var file = this.getRequestFile(request, null, true); 591 if (file) 592 { 593 logTime(file, "responseCompletedFile", time); 594 595 if (FBTrace.DBG_NET_EVENTS) 596 FBTrace.sysout("net.events.responseCompletedFile +" + time + " " + 597 getPrintableTime() + ", " + request.URI.path, file); 598 599 if (responseSize >= 0) 600 file.size = responseSize; 601 602 // This was only a helper to show download progress. 603 file.totalReceived = 0; 604 605 // The request is completed, get cache entry. 606 getCacheEntry(file, this); 607 608 // Sometimes the HTTP-ON-EXAMINE-RESPONSE doesn't come. 609 if (!file.loaded && file.responseHeadersText) 610 { 611 var info = null; 612 var m = file.responseHeadersText.match(reResponseStatus); 613 if (m.length == 3) 614 info = {responseStatus: m[1], responseStatusText: m[2]}; 615 this.respondedFile(request, NetUtils.now(), info); 616 } 617 618 this.updateIPInfo(request, file); 619 } 620 621 return file; 622 }, 623 624 closedFile: function closedFile(request, time) 625 { 626 var file = this.getRequestFile(request, null, true); 627 if (file) 628 { 629 logTime(file, "closedFile", time); 630 631 if (FBTrace.DBG_NET_EVENTS) 632 FBTrace.sysout("net.events.closedFile +" + time + " " + 633 getPrintableTime() + ", " + request.URI.path); 634 635 // If the response never came, stop the loading and set time info. 636 // In this case the request is marked with "Timeout" and the 637 // respondedTime is set to the time when ACTIVITY_SUBTYPE_TRANSACTION_CLOSE 638 // is received (after timeout). 639 // If file.responseHeadersText is null the response didn't come. 640 if (!file.loaded && !file.responseHeadersText) 641 { 642 if (FBTrace.DBG_NET_EVENTS) 643 FBTrace.sysout("net.events; TIMEOUT " + Http.safeGetRequestName(request)); 644 645 this.endLoad(file); 646 647 file.aborted = true; 648 if (!file.responseStatusText) 649 file.responseStatusText = "Aborted"; 650 651 if (!file.responseStarted) 652 { 653 file.respondedTime = time; 654 file.responseStarted = true; 655 } 656 657 file.endTime = time; 658 } 659 } 660 661 return file; 662 }, 663 664 resolvingFile: function resolvingFile(request, time) 665 { 666 var file = this.getRequestFile(request, null, true); 667 668 if (file) 669 logTime(file, "resolvingFile", time); 670 671 if (file && file.loaded) 672 return null; 673 674 if (file && !file.resolveStarted) 675 { 676 file.resolveStarted = true; 677 file.resolvingTime = time; 678 file.connectingTime = time; // in case connecting would never came. 679 file.connectedTime = time; // in case connected-to would never came. 680 file.sendingTime = time; // in case sending-to would never came. 681 file.waitingForTime = time; // in case waiting-for would never came. 682 } 683 684 return file; 685 }, 686 687 resolvedFile: function resolvedFile(request, time) 688 { 689 return null; 690 }, 691 692 stopFile: function stopFile(request, time, postText, responseText) 693 { 694 var file = this.getRequestFile(request, null, true); 695 if (file) 696 { 697 698 logTime(file, "stopFile", time); 699 700 if (FBTrace.DBG_NET_EVENTS) 701 FBTrace.sysout("net.stopFile +" + (NetUtils.now() - file.startTime) + " " + 702 getPrintableTime() + ", " + request.URI.path, file); 703 704 // xxxHonza: spy should measure time using the activity observer too. 705 // Don't ruin the endTime if it was already set. 706 if (file.endTime == file.startTime) 707 file.endTime = time; 708 709 file.postText = postText; 710 file.responseText = responseText; 711 712 NetUtils.getHttpHeaders(request, file, this.context); 713 714 this.endLoad(file); 715 716 getCacheEntry(file, this); 717 } 718 719 return file; 720 }, 721 722 abortFile: function abortFile(request, time, postText, responseText) 723 { 724 var file = this.getRequestFile(request, null, true); 725 if (file) 726 { 727 logTime(file, "abortFile", time); 728 729 file.aborted = true; 730 file.responseStatusText = "Aborted"; 731 } 732 733 return this.stopFile(request, time, postText, responseText); 734 }, 735 736 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 737 // IP Address and port number 738 739 updateIPInfo: function(request, file) 740 { 741 file.localAddress = Http.safeGetLocalAddress(request); 742 file.localPort = Http.safeGetLocalPort(request); 743 file.remoteAddress = Http.safeGetRemoteAddress(request); 744 file.remotePort = Http.safeGetRemotePort(request); 745 }, 746 747 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 748 749 windowPaint: function windowPaint(window, time) 750 { 751 if (FBTrace.DBG_NET) 752 FBTrace.sysout("net.windowPaint +? " + getPrintableTime() + ", " + 753 window.location.href, this.phases); 754 755 if (!this.phases.length) 756 return; 757 758 var phase = this.context.netProgress.currentPhase; 759 var timeStamp = phase.addTimeStamp("MozAfterPaint", "netPaintBar"); 760 timeStamp.time = time; 761 762 // Return the first file, so the layout is updated. I can happen that the 763 // onLoad event is the last one and the graph end-time must be recalculated. 764 return phase.files[0]; 765 }, 766 767 timeStamp: function timeStamp(window, time, label) 768 { 769 if (FBTrace.DBG_NET) 770 FBTrace.sysout("net.timeStamp +? " + getPrintableTime() + ", " + 771 window.location.href, this.phases); 772 773 if (!this.phases.length) 774 return; 775 776 var phase = this.context.netProgress.currentPhase; 777 var timeStamp = phase.addTimeStamp(label, "netTimeStampBar"); 778 timeStamp.time = time; 779 780 return phase.files[0]; 781 }, 782 783 windowLoad: function windowLoad(window, time) 784 { 785 if (FBTrace.DBG_NET_EVENTS) 786 FBTrace.sysout("net.windowLoad +? " + getPrintableTime() + ", " + 787 window.location.href, this.phases); 788 789 if (!this.phases.length) 790 return; 791 792 // Update all requests that belong to the first phase. 793 var firstPhase = this.phases[0]; 794 795 // Keep the information also in the phase for now, NetExport and other could need it. 796 firstPhase.windowLoadTime = time; 797 798 var timeStamp = firstPhase.addTimeStamp("load", "netWindowLoadBar"); 799 timeStamp.time = time; 800 801 // Return the first file, so the layout is updated. I can happen that the 802 // onLoad event is the last one and the graph end-time must be recalculated. 803 return firstPhase.files[0]; 804 }, 805 806 contentLoad: function contentLoad(window, time) 807 { 808 if (FBTrace.DBG_NET_EVENTS) 809 FBTrace.sysout("net.contentLoad +? " + getPrintableTime() + ", " + 810 window.location.href); 811 812 if (!this.phases.length) 813 return; 814 815 // Update all requests that belong to the first phase. 816 var firstPhase = this.phases[0]; 817 818 // Keep the information also in the phase for now, NetExport and other could need it. 819 firstPhase.contentLoadTime = time; 820 821 var timeStamp = firstPhase.addTimeStamp("DOMContentLoaded", "netContentLoadBar"); 822 timeStamp.time = time; 823 824 return null; 825 }, 826 827 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 828 829 getRequestFile: function getRequestFile(request, win, noCreate) 830 { 831 var name = Http.safeGetRequestName(request); 832 if (!name || reIgnore.exec(name)) 833 return null; 834 835 for (var i=0; i<this.files.length; i++) 836 { 837 var file = this.files[i]; 838 if (file.request == request) 839 return file; 840 } 841 842 if (noCreate) 843 return null; 844 845 if (!win || Win.getRootWindow(win) != this.context.window) 846 return; 847 848 var fileDoc = this.getRequestDocument(win); 849 var isDocument = request.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI && fileDoc.parent; 850 var doc = isDocument ? fileDoc.parent : fileDoc; 851 852 var file = doc.createFile(request); 853 if (isDocument) 854 { 855 fileDoc.documentFile = file; 856 file.ownDocument = fileDoc; 857 } 858 859 file.request = request; 860 file.requestNumber = this.requestNumber; 861 this.requestNumber++; 862 this.requests.push(request); 863 this.files.push(file); 864 865 if (FBTrace.DBG_NET_EVENTS) 866 FBTrace.sysout("net.createFile; " + Http.safeGetRequestName(request) + 867 "(" + this.files.length + ")"); 868 869 return file; 870 }, 871 872 getRequestDocument: function(win) 873 { 874 if (win) 875 { 876 var index = this.windows.indexOf(win); 877 if (index == -1) 878 { 879 var doc = new NetDocument(); 880 if (win.parent != win) 881 doc.parent = this.getRequestDocument(win.parent); 882 883 //doc.level = NetUtils.getFrameLevel(win); 884 885 this.documents.push(doc); 886 this.windows.push(win); 887 888 return doc; 889 } 890 else 891 return this.documents[index]; 892 } 893 else 894 return this.documents[0]; 895 }, 896 897 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 898 899 endLoad: function(file) 900 { 901 if (FBTrace.DBG_NET_EVENTS) 902 FBTrace.sysout("net.events.endLoad +" + (NetUtils.now() - file.startTime) + " " + 903 getPrintableTime() + ", " + file.request.URI.path, file); 904 905 // Set file as loaded. 906 file.loaded = true; 907 908 // Update last finished file of the associated phase. 909 //xxxHonza: verify this. 910 if (file.phase) 911 file.phase.lastFinishedFile = file; 912 }, 913 914 extendPhase: function(file) 915 { 916 // Phase start can be measured since HTTP-ON-MODIFIED-REQUEST as 917 // ACTIVITY_SUBTYPE_REQUEST_HEADER won't fire if the response comes from the BF cache. 918 // If it's real HTTP request we need to start again since ACTIVITY_SUBTYPE_REQUEST_HEADER 919 // has the proper time. 920 // Order of ACTIVITY_SUBTYPE_REQUEST_HEADER can be different than order of 921 // HTTP-ON-MODIFIED-REQUEST events, see issue 4535 922 if (file.phase) 923 { 924 if (file.phase.files[0] == file) 925 file.phase.startTime = file.startTime; 926 927 // Since the request order can be wrong (see above) we need to iterate all files 928 // in this phase and find the one that actually executed first. 929 // In some cases, the waterfall can display a request executed before another, 930 // but started later. 931 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=664781 932 var phase = file.phase; 933 for (var i=0; i<phase.files.length; i++) 934 { 935 var file = phase.files[i]; 936 if (file.startTime > 0 && phase.startTime > file.startTime) 937 phase.startTime = file.startTime; 938 } 939 return; 940 } 941 942 if (this.currentPhase) 943 { 944 // If the new request has been started within a "phaseInterval" after the 945 // previous reqeust has been started, associate it with the current phase; 946 // otherwise create a new phase. 947 var phaseInterval = Firebug.netPhaseInterval; 948 var lastStartTime = this.currentPhase.lastStartTime; 949 if (phaseInterval > 0 && this.loaded && file.startTime - lastStartTime >= phaseInterval) 950 this.startPhase(file); 951 else 952 this.currentPhase.addFile(file); 953 } 954 else 955 { 956 // If there is no phase yet, just create it. 957 this.startPhase(file); 958 } 959 }, 960 961 startPhase: function(file) 962 { 963 var phase = new NetPhase(file); 964 phase.initial = !this.currentPhase; 965 966 file.breakLayout = true; 967 968 this.currentPhase = phase; 969 this.phases.push(phase); 970 }, 971 }; 972 973 // ********************************************************************************************* // 974 // Time Logging 975 976 function logTime(file, title, time) 977 { 978 // xxxHonza: just for debugging purposes. 979 return; 980 981 if (!file._timings) 982 file._timings = {counter: 0}; 983 984 if (!file._timings.logs) 985 file._timings.logs = []; 986 987 file._timings.logs.push({ 988 title: title, 989 index: ++file._timings.counter, 990 time: time 991 }); 992 } 993 994 // ********************************************************************************************* // 995 996 /** 997 * A Document is a helper object that represents a document (window) on the page. 998 * This object is created for main page document and for every embedded document (iframe) 999 * for which a request is made. 1000 */ 1001 function NetDocument() 1002 { 1003 this.id = 0; 1004 this.title = ""; 1005 } 1006 1007 NetDocument.prototype = 1008 { 1009 createFile: function(request) 1010 { 1011 return new NetFile(request.name, this); 1012 } 1013 }; 1014 1015 // ********************************************************************************************* // 1016 1017 /** 1018 * A File is a helper object that represents a file for which a request is made. 1019 * The document refers to it's parent document (NetDocument) through a member 1020 * variable. 1021 */ 1022 function NetFile(href, document) 1023 { 1024 this.href = href; 1025 this.document = document; 1026 } 1027 1028 NetFile.prototype = 1029 { 1030 status: 0, 1031 files: 0, 1032 loaded: false, 1033 fromCache: false, 1034 size: -1, 1035 expectedSize: -1, 1036 endTime: null, 1037 waitingForTime: null, 1038 connectingTime: null, 1039 1040 getFileLink: function(message) 1041 { 1042 // this.SourceLink = function(url, line, type, object, instance) 1043 var link = new SourceLink.SourceLink(this.href, null, "net", this.request); 1044 return link; 1045 }, 1046 1047 getFileURL: function() 1048 { 1049 var index = this.href.indexOf("?"); 1050 if (index < 0) 1051 return this.href; 1052 1053 return this.href.substring(0, index); 1054 }, 1055 1056 clear: function() 1057 { 1058 // Remove all members to avoid circular references and memleaks. 1059 for (var name in this) 1060 delete this[name]; 1061 } 1062 }; 1063 1064 Firebug.NetFile = NetFile; 1065 1066 // ********************************************************************************************* // 1067 1068 /** 1069 * A Phase is a helper object that groups requests made in the same time frame. 1070 * In other words, if a new requests is started within a given time (specified 1071 * by phaseInterval [ms]) - after previous request has been started - 1072 * it automatically belongs to the same phase. 1073 * If a request is started after this period, a new phase is created 1074 * and this file becomes to be the first in that phase. 1075 * The first phase is ended when the page finishes it's loading. Other phases 1076 * might be started by additional XHR made by the page. 1077 * 1078 * All phases are stored within NetProgress.phases array. 1079 * 1080 * Phases are used to compute size of the graphical timeline. The timeline 1081 * for each phase starts from the beginning of the graph. 1082 */ 1083 function NetPhase(file) 1084 { 1085 // Start time of the phase. Remains the same, even if the file 1086 // is removed from the log (due to a max limit of entries). 1087 // This ensures stability of the time line. 1088 this.startTime = file.startTime; 1089 1090 // The last finished request (file) in the phase. 1091 this.lastFinishedFile = null; 1092 1093 // Set to true if the phase needs to be updated in the UI. 1094 this.invalidPhase = null; 1095 1096 // List of files associated with this phase. 1097 this.files = []; 1098 1099 // List of paint events. 1100 this.windowPaints = []; 1101 1102 this.timeStamps = []; 1103 1104 this.addFile(file); 1105 } 1106 1107 NetPhase.prototype = 1108 { 1109 addFile: function(file) 1110 { 1111 this.files.push(file); 1112 file.phase = this; 1113 }, 1114 1115 removeFile: function removeFile(file) 1116 { 1117 Arr.remove(this.files, file); 1118 1119 // The file don't have a parent phase now. 1120 file.phase = null; 1121 1122 // If the last file has been removed, update the last file member. 1123 if (file == this.lastFinishedFile) 1124 { 1125 if (this.files.length == 0) 1126 { 1127 this.lastFinishedFile = null; 1128 } 1129 else 1130 { 1131 for (var i=0; i<this.files.length; i++) 1132 { 1133 if (this.lastFinishedFile.endTime < this.files[i].endTime) 1134 this.lastFinishedFile = this.files[i]; 1135 } 1136 } 1137 } 1138 }, 1139 1140 get lastStartTime() 1141 { 1142 return this.files[this.files.length - 1].startTime; 1143 }, 1144 1145 get endTime() 1146 { 1147 var endTime = this.lastFinishedFile ? this.lastFinishedFile.endTime : null; 1148 if (this.timeStamps.length > 0) 1149 { 1150 var lastTimeStamp = this.timeStamps[this.timeStamps.length-1].time; 1151 endTime = (endTime > lastTimeStamp) ? endTime : lastTimeStamp; 1152 } 1153 return endTime; 1154 }, 1155 1156 addTimeStamp: function(label, classes) 1157 { 1158 var timeStamp = { 1159 label: label, 1160 classes: classes 1161 }; 1162 1163 this.timeStamps.push(timeStamp); 1164 return timeStamp; 1165 } 1166 }; 1167 1168 // ********************************************************************************************* // 1169 1170 function getCacheEntry(file, netProgress) 1171 { 1172 // Bail out if the cache is disabled. 1173 if (!Firebug.NetMonitor.BrowserCache.isEnabled()) 1174 return; 1175 1176 // Don't request the cache entry twice. 1177 if (file.cacheEntryRequested) 1178 return; 1179 1180 file.cacheEntryRequested = true; 1181 1182 if (FBTrace.DBG_NET_EVENTS) 1183 FBTrace.sysout("net.getCacheEntry for file.href: " + file.href + "\n"); 1184 1185 // Pause first because this is usually called from stopFile, at which point 1186 // the file's cache entry is locked 1187 setTimeout(function() 1188 { 1189 try 1190 { 1191 delayGetCacheEntry(file, netProgress); 1192 } 1193 catch (exc) 1194 { 1195 if (exc.name != "NS_ERROR_CACHE_KEY_NOT_FOUND") 1196 { 1197 if (FBTrace.DBG_ERRORS) 1198 FBTrace.sysout("net.delayGetCacheEntry FAILS " + file.href, exc); 1199 } 1200 } 1201 }); 1202 } 1203 1204 function delayGetCacheEntry(file, netProgress) 1205 { 1206 if (FBTrace.DBG_NET_EVENTS) 1207 FBTrace.sysout("net.delayGetCacheEntry for file.href=" + file.href + "\n"); 1208 1209 // Init cache session. 1210 if (!cacheSession) 1211 { 1212 var cacheService = CacheService.getService(Ci.nsICacheService); 1213 cacheSession = cacheService.createSession("HTTP", Ci.nsICache.STORE_ANYWHERE, true); 1214 cacheSession.doomEntriesIfExpired = false; 1215 } 1216 1217 cacheSession.asyncOpenCacheEntry(file.href, Ci.nsICache.ACCESS_READ, 1218 { 1219 onCacheEntryAvailable: function(descriptor, accessGranted, status) 1220 { 1221 if (FBTrace.DBG_NET_EVENTS) 1222 FBTrace.sysout("net.onCacheEntryAvailable for file.href=" + file.href + "\n"); 1223 1224 if (descriptor) 1225 { 1226 if (file.size <= 0) 1227 file.size = descriptor.dataSize; 1228 1229 if (descriptor.lastModified && descriptor.lastFetched && 1230 descriptor.lastModified < Math.floor(file.startTime/1000)) { 1231 file.fromCache = true; 1232 } 1233 1234 file.cacheEntry = [ 1235 { name: "Last Modified", 1236 value: NetUtils.getDateFromSeconds(descriptor.lastModified) 1237 }, 1238 { name: "Last Fetched", 1239 value: NetUtils.getDateFromSeconds(descriptor.lastFetched) 1240 }, 1241 { name: "Expires", 1242 value: NetUtils.getDateFromSeconds(descriptor.expirationTime) 1243 }, 1244 { name: "Data Size", 1245 value: descriptor.dataSize 1246 }, 1247 { name: "Fetch Count", 1248 value: descriptor.fetchCount 1249 }, 1250 { name: "Device", 1251 value: descriptor.deviceID 1252 } 1253 ]; 1254 1255 // Get contentType from the cache. 1256 try 1257 { 1258 var value = descriptor.getMetaDataElement("response-head"); 1259 var contentType = getContentTypeFromResponseHead(value); 1260 file.mimeType = NetUtils.getMimeType(contentType, file.href); 1261 } 1262 catch (e) 1263 { 1264 if (FBTrace.DBG_ERRORS) 1265 FBTrace.sysout("net.delayGetCacheEntry; EXCEPTION ", e); 1266 } 1267 1268 descriptor.close(); 1269 netProgress.update(file); 1270 } 1271 1272 getCachedHeaders(file); 1273 } 1274 }); 1275 } 1276 1277 function getCachedHeaders(file) 1278 { 1279 // Cached headers are important only if the reqeust comes from the cache. 1280 if (!file.fromCache) 1281 return; 1282 1283 // The request is containing cached headers now. These will be also displayed 1284 // within the Net panel. 1285 var cache = {}; 1286 NetUtils.getHttpHeaders(file.request, cache); 1287 file.cachedResponseHeaders = cache.responseHeaders; 1288 } 1289 1290 function getContentTypeFromResponseHead(value) 1291 { 1292 var values = value.split("\r\n"); 1293 for (var i=0; i<values.length; i++) 1294 { 1295 var option = values[i].split(": "); 1296 var headerName = option[0]; 1297 if (headerName && headerName.toLowerCase() == "content-type") 1298 return option[1]; 1299 } 1300 } 1301 1302 // ********************************************************************************************* // 1303 // Helper for tracing 1304 1305 function getPrintableTime() 1306 { 1307 var date = new Date(); 1308 return "(" + date.getSeconds() + ":" + date.getMilliseconds() + ")"; 1309 } 1310 1311 // ********************************************************************************************* // 1312 // Registration 1313 1314 return NetProgress; 1315 1316 // ********************************************************************************************* // 1317 }); 1318