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