1 /* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2008, 2009 Anthony Ricaud <rik (at) webkit.org> 4 * Copyright (C) 2011 Google Inc. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 16 * its contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 WebInspector.NetworkPanel = function() 32 { 33 WebInspector.Panel.call(this, "network"); 34 35 this.createSidebar(); 36 this.sidebarElement.className = "network-sidebar"; 37 38 this._resources = []; 39 this._resourcesById = {}; 40 this._resourcesByURL = {}; 41 this._staleResources = []; 42 this._resourceGridNodes = {}; 43 this._lastResourceGridNodeId = 0; 44 this._mainResourceLoadTime = -1; 45 this._mainResourceDOMContentTime = -1; 46 this._hiddenCategories = {}; 47 48 this._categories = WebInspector.resourceCategories; 49 50 this.containerElement = document.createElement("div"); 51 this.containerElement.id = "network-container"; 52 this.sidebarElement.appendChild(this.containerElement); 53 54 this._viewsContainerElement = document.createElement("div"); 55 this._viewsContainerElement.id = "network-views"; 56 this._viewsContainerElement.className = "hidden"; 57 this.element.appendChild(this._viewsContainerElement); 58 59 this._closeButtonElement = document.createElement("button"); 60 this._closeButtonElement.id = "network-close-button"; 61 this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false); 62 this._viewsContainerElement.appendChild(this._closeButtonElement); 63 64 this._createSortingFunctions(); 65 this._createTable(); 66 this._createTimelineGrid(); 67 this._createStatusbarButtons(); 68 this._createFilterStatusBarItems(); 69 this._createSummaryBar(); 70 71 if (!WebInspector.settings.resourcesLargeRows) 72 this._setLargerResources(WebInspector.settings.resourcesLargeRows); 73 74 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true); 75 // Enable faster hint. 76 this._popoverHelper.setTimeout(100); 77 78 this.calculator = new WebInspector.NetworkTransferTimeCalculator(); 79 this._filter(this._filterAllElement, false); 80 81 this._toggleGridMode(); 82 83 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this); 84 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._onResourceUpdated, this); 85 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceUpdated, this); 86 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.FrameCommittedLoad, this._onFrameCommitLoad, this); 87 } 88 89 WebInspector.NetworkPanel.prototype = { 90 get toolbarItemLabel() 91 { 92 return WebInspector.UIString("Network"); 93 }, 94 95 get statusBarItems() 96 { 97 return [this._largerResourcesButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement]; 98 }, 99 100 isCategoryVisible: function(categoryName) 101 { 102 return true; 103 }, 104 105 elementsToRestoreScrollPositionsFor: function() 106 { 107 return [this.containerElement, this._dataGrid.scrollContainer]; 108 }, 109 110 resize: function() 111 { 112 WebInspector.Panel.prototype.resize.call(this); 113 this._dataGrid.updateWidths(); 114 this._updateOffscreenRows(); 115 }, 116 117 updateSidebarWidth: function(width) 118 { 119 if (!this._viewingResourceMode) 120 return; 121 WebInspector.Panel.prototype.updateSidebarWidth.call(this, width); 122 }, 123 124 updateMainViewWidth: function(width) 125 { 126 this._viewsContainerElement.style.left = width + "px"; 127 }, 128 129 handleShortcut: function(event) 130 { 131 if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { 132 this._toggleGridMode(); 133 event.handled = true; 134 } 135 }, 136 137 _createTimelineGrid: function() 138 { 139 this._timelineGrid = new WebInspector.TimelineGrid(); 140 this._timelineGrid.element.addStyleClass("network-timeline-grid"); 141 this._dataGrid.element.appendChild(this._timelineGrid.element); 142 }, 143 144 _createTable: function() 145 { 146 var columns = {name: {}, method: {}, status: {}, type: {}, size: {}, time: {}, timeline: {}}; 147 columns.name.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path")); 148 columns.name.sortable = true; 149 columns.name.width = "20%"; 150 columns.name.disclosure = true; 151 152 columns.method.title = WebInspector.UIString("Method"); 153 columns.method.sortable = true; 154 columns.method.width = "7%"; 155 156 columns.status.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text")); 157 columns.status.sortable = true; 158 columns.status.width = "8%"; 159 160 columns.type.title = WebInspector.UIString("Type"); 161 columns.type.sortable = true; 162 columns.type.width = "10%"; 163 164 columns.size.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Transfer")); 165 columns.size.sortable = true; 166 columns.size.width = "10%"; 167 columns.size.aligned = "right"; 168 169 columns.time.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency")); 170 columns.time.sortable = true; 171 columns.time.width = "10%"; 172 columns.time.aligned = "right"; 173 174 columns.timeline.title = ""; 175 columns.timeline.sortable = false; 176 columns.timeline.width = "37%"; 177 columns.timeline.sort = "ascending"; 178 179 this._dataGrid = new WebInspector.DataGrid(columns); 180 this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true); 181 this.containerElement.appendChild(this._dataGrid.element); 182 this._dataGrid.addEventListener("sorting changed", this._sortItems, this); 183 this._dataGrid.addEventListener("width changed", this._updateDividersIfNeeded, this); 184 this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this)); 185 186 this._patchTimelineHeader(); 187 }, 188 189 _makeHeaderFragment: function(title, subtitle) 190 { 191 var fragment = document.createDocumentFragment(); 192 fragment.appendChild(document.createTextNode(title)); 193 var subtitleDiv = document.createElement("div"); 194 subtitleDiv.className = "network-header-subtitle"; 195 subtitleDiv.textContent = subtitle; 196 fragment.appendChild(subtitleDiv); 197 return fragment; 198 }, 199 200 _patchTimelineHeader: function() 201 { 202 var timelineSorting = document.createElement("select"); 203 204 var option = document.createElement("option"); 205 option.value = "startTime"; 206 option.label = WebInspector.UIString("Timeline"); 207 timelineSorting.appendChild(option); 208 209 option = document.createElement("option"); 210 option.value = "startTime"; 211 option.label = WebInspector.UIString("Start Time"); 212 timelineSorting.appendChild(option); 213 214 option = document.createElement("option"); 215 option.value = "responseTime"; 216 option.label = WebInspector.UIString("Response Time"); 217 timelineSorting.appendChild(option); 218 219 option = document.createElement("option"); 220 option.value = "endTime"; 221 option.label = WebInspector.UIString("End Time"); 222 timelineSorting.appendChild(option); 223 224 option = document.createElement("option"); 225 option.value = "duration"; 226 option.label = WebInspector.UIString("Duration"); 227 timelineSorting.appendChild(option); 228 229 option = document.createElement("option"); 230 option.value = "latency"; 231 option.label = WebInspector.UIString("Latency"); 232 timelineSorting.appendChild(option); 233 234 var header = this._dataGrid.headerTableHeader("timeline"); 235 header.replaceChild(timelineSorting, header.firstChild); 236 237 timelineSorting.addEventListener("click", function(event) { event.stopPropagation() }, false); 238 timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false); 239 this._timelineSortSelector = timelineSorting; 240 }, 241 242 _createSortingFunctions: function() 243 { 244 this._sortingFunctions = {}; 245 this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator; 246 this._sortingFunctions.method = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "method", false); 247 this._sortingFunctions.status = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "statusCode", false); 248 this._sortingFunctions.type = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "mimeType", false); 249 this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator; 250 this._sortingFunctions.time = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", false); 251 this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false); 252 this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false); 253 this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "endTime", false); 254 this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "responseReceivedTime", false); 255 this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", true); 256 this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "latency", true); 257 258 var timeCalculator = new WebInspector.NetworkTransferTimeCalculator(); 259 var durationCalculator = new WebInspector.NetworkTransferDurationCalculator(); 260 261 this._calculators = {}; 262 this._calculators.timeline = timeCalculator; 263 this._calculators.startTime = timeCalculator; 264 this._calculators.endTime = timeCalculator; 265 this._calculators.responseTime = timeCalculator; 266 this._calculators.duration = durationCalculator; 267 this._calculators.latency = durationCalculator; 268 }, 269 270 _sortItems: function() 271 { 272 var columnIdentifier = this._dataGrid.sortColumnIdentifier; 273 if (columnIdentifier === "timeline") { 274 this._sortByTimeline(); 275 return; 276 } 277 var sortingFunction = this._sortingFunctions[columnIdentifier]; 278 if (!sortingFunction) 279 return; 280 281 this._dataGrid.sortNodes(sortingFunction, this._dataGrid.sortOrder === "descending"); 282 this._timelineSortSelector.selectedIndex = 0; 283 this._updateOffscreenRows(); 284 }, 285 286 _sortByTimeline: function() 287 { 288 var selectedIndex = this._timelineSortSelector.selectedIndex; 289 if (!selectedIndex) 290 selectedIndex = 1; // Sort by start time by default. 291 var selectedOption = this._timelineSortSelector[selectedIndex]; 292 var value = selectedOption.value; 293 294 var sortingFunction = this._sortingFunctions[value]; 295 this._dataGrid.sortNodes(sortingFunction); 296 this.calculator = this._calculators[value]; 297 if (this.calculator.startAtZero) 298 this._timelineGrid.hideEventDividers(); 299 else 300 this._timelineGrid.showEventDividers(); 301 this._dataGrid.markColumnAsSortedBy("timeline", "ascending"); 302 this._updateOffscreenRows(); 303 }, 304 305 _createFilterStatusBarItems: function() 306 { 307 var filterBarElement = document.createElement("div"); 308 filterBarElement.className = "scope-bar status-bar-item"; 309 filterBarElement.id = "network-filter"; 310 311 function createFilterElement(category, label) 312 { 313 var categoryElement = document.createElement("li"); 314 categoryElement.category = category; 315 categoryElement.className = category; 316 categoryElement.appendChild(document.createTextNode(label)); 317 categoryElement.addEventListener("click", this._updateFilter.bind(this), false); 318 filterBarElement.appendChild(categoryElement); 319 320 return categoryElement; 321 } 322 323 this._filterAllElement = createFilterElement.call(this, "all", WebInspector.UIString("All")); 324 325 // Add a divider 326 var dividerElement = document.createElement("div"); 327 dividerElement.addStyleClass("scope-bar-divider"); 328 filterBarElement.appendChild(dividerElement); 329 330 for (var category in this._categories) 331 createFilterElement.call(this, category, this._categories[category].title); 332 this._filterBarElement = filterBarElement; 333 }, 334 335 _createSummaryBar: function() 336 { 337 var tbody = this._dataGrid.dataTableBody; 338 var tfoot = document.createElement("tfoot"); 339 var tr = tfoot.createChild("tr", "revealed network-summary-bar"); 340 var td = tr.createChild("td"); 341 td.setAttribute("colspan", 7); 342 tbody.parentNode.insertBefore(tfoot, tbody); 343 this._summaryBarElement = td; 344 }, 345 346 _updateSummaryBar: function() 347 { 348 var numRequests = this._resources.length; 349 350 if (!numRequests) { 351 if (this._summaryBarElement._isDisplayingWarning) 352 return; 353 this._summaryBarElement._isDisplayingWarning = true; 354 355 var img = document.createElement("img"); 356 img.src = "Images/warningIcon.png"; 357 this._summaryBarElement.removeChildren(); 358 this._summaryBarElement.appendChild(img); 359 this._summaryBarElement.appendChild(document.createTextNode( 360 WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity."))); 361 return; 362 } 363 delete this._summaryBarElement._isDisplayingWarning; 364 365 var transferSize = 0; 366 var baseTime = -1; 367 var maxTime = -1; 368 for (var i = 0; i < this._resources.length; ++i) { 369 var resource = this._resources[i]; 370 transferSize += (resource.cached || !resource.transferSize) ? 0 : resource.transferSize; 371 if (resource === WebInspector.mainResource) 372 baseTime = resource.startTime; 373 if (resource.endTime > maxTime) 374 maxTime = resource.endTime; 375 } 376 var text = String.sprintf(WebInspector.UIString("%d requests"), numRequests); 377 text += " \u2758 " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize)); 378 if (baseTime !== -1 && this._mainResourceLoadTime !== -1 && this._mainResourceDOMContentTime !== -1 && this._mainResourceDOMContentTime > baseTime) { 379 text += " \u2758 " + String.sprintf(WebInspector.UIString("%s (onload: %s, DOMContentLoaded: %s)"), 380 Number.secondsToString(maxTime - baseTime), 381 Number.secondsToString(this._mainResourceLoadTime - baseTime), 382 Number.secondsToString(this._mainResourceDOMContentTime - baseTime)); 383 } 384 this._summaryBarElement.textContent = text; 385 }, 386 387 _showCategory: function(category) 388 { 389 this._dataGrid.element.addStyleClass("filter-" + category); 390 delete this._hiddenCategories[category]; 391 }, 392 393 _hideCategory: function(category) 394 { 395 this._dataGrid.element.removeStyleClass("filter-" + category); 396 this._hiddenCategories[category] = true; 397 }, 398 399 _updateFilter: function(e) 400 { 401 var isMac = WebInspector.isMac(); 402 var selectMultiple = false; 403 if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) 404 selectMultiple = true; 405 if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) 406 selectMultiple = true; 407 408 this._filter(e.target, selectMultiple); 409 }, 410 411 _filter: function(target, selectMultiple) 412 { 413 function unselectAll() 414 { 415 for (var i = 0; i < this._filterBarElement.childNodes.length; ++i) { 416 var child = this._filterBarElement.childNodes[i]; 417 if (!child.category) 418 continue; 419 420 child.removeStyleClass("selected"); 421 this._hideCategory(child.category); 422 } 423 } 424 425 if (target.category === this._filterAllElement) { 426 if (target.hasStyleClass("selected")) { 427 // We can't unselect All, so we break early here 428 return; 429 } 430 431 // If All wasn't selected, and now is, unselect everything else. 432 unselectAll.call(this); 433 } else { 434 // Something other than All is being selected, so we want to unselect All. 435 if (this._filterAllElement.hasStyleClass("selected")) { 436 this._filterAllElement.removeStyleClass("selected"); 437 this._hideCategory("all"); 438 } 439 } 440 441 if (!selectMultiple) { 442 // If multiple selection is off, we want to unselect everything else 443 // and just select ourselves. 444 unselectAll.call(this); 445 446 target.addStyleClass("selected"); 447 this._showCategory(target.category); 448 this._updateOffscreenRows(); 449 return; 450 } 451 452 if (target.hasStyleClass("selected")) { 453 // If selectMultiple is turned on, and we were selected, we just 454 // want to unselect ourselves. 455 target.removeStyleClass("selected"); 456 this._hideCategory(target.category); 457 } else { 458 // If selectMultiple is turned on, and we weren't selected, we just 459 // want to select ourselves. 460 target.addStyleClass("selected"); 461 this._showCategory(target.category); 462 } 463 this._updateOffscreenRows(); 464 }, 465 466 _scheduleRefresh: function() 467 { 468 if (this._needsRefresh) 469 return; 470 471 this._needsRefresh = true; 472 473 if (this.visible && !("_refreshTimeout" in this)) 474 this._refreshTimeout = setTimeout(this.refresh.bind(this), 500); 475 }, 476 477 _updateDividersIfNeeded: function(force) 478 { 479 var timelineColumn = this._dataGrid.columns.timeline; 480 for (var i = 0; i < this._dataGrid.resizers.length; ++i) { 481 if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) { 482 // Position timline grid location. 483 this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left; 484 this._timelineGrid.element.style.right = "18px"; 485 } 486 } 487 488 var proceed = true; 489 if (!this.visible) { 490 this._scheduleRefresh(); 491 proceed = false; 492 } else 493 proceed = this._timelineGrid.updateDividers(force, this.calculator); 494 495 if (!proceed) 496 return; 497 498 if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) { 499 // If our current sorting method starts at zero, that means it shows all 500 // resources starting at the same point, and so onLoad event and DOMContent 501 // event lines really wouldn't make much sense here, so don't render them. 502 // Additionally, if the calculator doesn't have the computePercentageFromEventTime 503 // function defined, we are probably sorting by size, and event times aren't relevant 504 // in this case. 505 return; 506 } 507 508 this._timelineGrid.removeEventDividers(); 509 if (this._mainResourceLoadTime !== -1) { 510 var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime); 511 512 var loadDivider = document.createElement("div"); 513 loadDivider.className = "network-event-divider network-red-divider"; 514 515 var loadDividerPadding = document.createElement("div"); 516 loadDividerPadding.className = "network-event-divider-padding"; 517 loadDividerPadding.title = WebInspector.UIString("Load event fired"); 518 loadDividerPadding.appendChild(loadDivider); 519 loadDividerPadding.style.left = percent + "%"; 520 this._timelineGrid.addEventDivider(loadDividerPadding); 521 } 522 523 if (this._mainResourceDOMContentTime !== -1) { 524 var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime); 525 526 var domContentDivider = document.createElement("div"); 527 domContentDivider.className = "network-event-divider network-blue-divider"; 528 529 var domContentDividerPadding = document.createElement("div"); 530 domContentDividerPadding.className = "network-event-divider-padding"; 531 domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired"); 532 domContentDividerPadding.appendChild(domContentDivider); 533 domContentDividerPadding.style.left = percent + "%"; 534 this._timelineGrid.addEventDivider(domContentDividerPadding); 535 } 536 }, 537 538 _refreshIfNeeded: function() 539 { 540 if (this._needsRefresh) 541 this.refresh(); 542 }, 543 544 _invalidateAllItems: function() 545 { 546 this._staleResources = this._resources.slice(); 547 }, 548 549 get calculator() 550 { 551 return this._calculator; 552 }, 553 554 set calculator(x) 555 { 556 if (!x || this._calculator === x) 557 return; 558 559 this._calculator = x; 560 this._calculator.reset(); 561 562 this._invalidateAllItems(); 563 this.refresh(); 564 }, 565 566 _resourceGridNode: function(resource) 567 { 568 return this._resourceGridNodes[resource.__gridNodeId]; 569 }, 570 571 _createResourceGridNode: function(resource) 572 { 573 var node = new WebInspector.NetworkDataGridNode(this, resource); 574 resource.__gridNodeId = this._lastResourceGridNodeId++; 575 this._resourceGridNodes[resource.__gridNodeId] = node; 576 return node; 577 }, 578 579 revealAndSelectItem: function(resource) 580 { 581 var node = this._resourceGridNode(resource); 582 if (node) { 583 node.reveal(); 584 node.select(true); 585 } 586 }, 587 588 addEventDivider: function(divider) 589 { 590 this._timelineGrid.addEventDivider(divider); 591 }, 592 593 _createStatusbarButtons: function() 594 { 595 this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item"); 596 this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked.bind(this), false); 597 598 this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); 599 this._clearButton.addEventListener("click", this._reset.bind(this), false); 600 601 this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item"); 602 this._largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows; 603 this._largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false); 604 }, 605 606 set mainResourceLoadTime(x) 607 { 608 if (this._mainResourceLoadTime === x) 609 return; 610 611 this._mainResourceLoadTime = x || -1; 612 // Update the dividers to draw the new line 613 this._updateDividersIfNeeded(true); 614 }, 615 616 set mainResourceDOMContentTime(x) 617 { 618 if (this._mainResourceDOMContentTime === x) 619 return; 620 621 this._mainResourceDOMContentTime = x || -1; 622 this._updateDividersIfNeeded(true); 623 }, 624 625 show: function() 626 { 627 WebInspector.Panel.prototype.show.call(this); 628 this._refreshIfNeeded(); 629 630 if (this.visibleView) 631 this.visibleView.show(this._viewsContainerElement); 632 633 this._dataGrid.updateWidths(); 634 }, 635 636 hide: function() 637 { 638 WebInspector.Panel.prototype.hide.call(this); 639 this._popoverHelper.hidePopup(); 640 }, 641 642 get searchableViews() 643 { 644 var views = []; 645 return views; 646 }, 647 648 searchMatchFound: function(view, matches) 649 { 650 this._resourceGridNode(view.resource).searchMatches = matches; 651 }, 652 653 searchCanceled: function(startingNewSearch) 654 { 655 WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); 656 657 if (startingNewSearch || !this._resources) 658 return; 659 }, 660 661 performSearch: function(query) 662 { 663 WebInspector.Panel.prototype.performSearch.call(this, query); 664 }, 665 666 refresh: function() 667 { 668 this._needsRefresh = false; 669 if ("_refreshTimeout" in this) { 670 clearTimeout(this._refreshTimeout); 671 delete this._refreshTimeout; 672 } 673 674 var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow(); 675 var staleItemsLength = this._staleResources.length; 676 var boundariesChanged = false; 677 678 for (var i = 0; i < staleItemsLength; ++i) { 679 var resource = this._staleResources[i]; 680 var node = this._resourceGridNode(resource); 681 if (!node) { 682 // Create the timeline tree element and graph. 683 node = this._createResourceGridNode(resource); 684 this._dataGrid.appendChild(node); 685 } 686 node.refreshResource(); 687 688 if (this.calculator.updateBoundaries(resource)) 689 boundariesChanged = true; 690 } 691 692 if (boundariesChanged) { 693 // The boundaries changed, so all item graphs are stale. 694 this._invalidateAllItems(); 695 staleItemsLength = this._staleResources.length; 696 } 697 698 for (var i = 0; i < staleItemsLength; ++i) 699 this._resourceGridNode(this._staleResources[i]).refreshGraph(this.calculator); 700 701 this._staleResources = []; 702 this._sortItems(); 703 this._updateSummaryBar(); 704 this._updateOffscreenRows(); 705 this._dataGrid.updateWidths(); 706 707 if (wasScrolledToLastRow) 708 this._dataGrid.scrollToLastRow(); 709 }, 710 711 _onPreserveLogClicked: function(e) 712 { 713 this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled; 714 }, 715 716 _reset: function() 717 { 718 this._popoverHelper.hidePopup(); 719 this._closeVisibleResource(); 720 721 this._toggleGridMode(); 722 723 // Begin reset timeline 724 if (this._calculator) 725 this._calculator.reset(); 726 727 this._resources = []; 728 this._resourcesById = {}; 729 this._resourcesByURL = {}; 730 this._staleResources = []; 731 this._resourceGridNodes = {}; 732 733 this._dataGrid.removeChildren(); 734 this._updateDividersIfNeeded(true); 735 // End reset timeline. 736 737 this._mainResourceLoadTime = -1; 738 this._mainResourceDOMContentTime = -1; 739 740 this._viewsContainerElement.removeChildren(); 741 this._viewsContainerElement.appendChild(this._closeButtonElement); 742 this._updateSummaryBar(); 743 WebInspector.extensionServer.resetResources(); 744 }, 745 746 get resources() 747 { 748 return this._resources; 749 }, 750 751 resourceById: function(id) 752 { 753 return this._resourcesById[id]; 754 }, 755 756 _onResourceStarted: function(event) 757 { 758 this._appendResource(event.data); 759 }, 760 761 _appendResource: function(resource) 762 { 763 this._resources.push(resource); 764 this._resourcesById[resource.identifier] = resource; 765 this._resourcesByURL[resource.url] = resource; 766 767 // Pull all the redirects of the main resource upon commit load. 768 if (resource.redirects) { 769 for (var i = 0; i < resource.redirects.length; ++i) 770 this._refreshResource(resource.redirects[i]); 771 } 772 773 this._refreshResource(resource); 774 }, 775 776 _onResourceUpdated: function(event) 777 { 778 this._refreshResource(event.data); 779 }, 780 781 _refreshResource: function(resource) 782 { 783 this._staleResources.push(resource); 784 this._scheduleRefresh(); 785 786 var oldView = WebInspector.ResourceView.existingResourceViewForResource(resource); 787 if (!oldView) 788 return; 789 790 if (WebInspector.ResourceView.resourceViewTypeMatchesResource(resource)) 791 return; 792 793 var newView = WebInspector.ResourceView.recreateResourceView(resource); 794 if (this.visibleView === oldView) 795 this.visibleView = newView; 796 }, 797 798 clear: function() 799 { 800 if (this._preserveLogToggle.toggled) 801 return; 802 this._reset(); 803 }, 804 805 _onFrameCommitLoad: function(event) 806 { 807 if (event.data.frame.parentId) 808 return; 809 810 // Main frame committed load. 811 if (this._preserveLogToggle.toggled) 812 return; 813 814 // Preserve provisional load resources. 815 var loaderId = event.data.loaderId; 816 var resourcesToPreserve = []; 817 for (var i = 0; i < this._resources.length; ++i) { 818 var resource = this._resources[i]; 819 if (resource.loaderId === loaderId) 820 resourcesToPreserve.push(resource); 821 } 822 823 this._reset(); 824 825 // Restore preserved items. 826 for (var i = 0; i < resourcesToPreserve.length; ++i) 827 this._appendResource(resourcesToPreserve[i]); 828 }, 829 830 canShowAnchorLocation: function(anchor) 831 { 832 return !!this._resourcesByURL[anchor.href]; 833 }, 834 835 showAnchorLocation: function(anchor) 836 { 837 this._showResource(this._resourcesByURL[anchor.href], anchor.getAttribute("line_number") - 1); 838 }, 839 840 _showResource: function(resource, line) 841 { 842 if (!resource) 843 return; 844 845 this._popoverHelper.hidePopup(); 846 847 this._toggleViewingResourceMode(); 848 849 if (this.visibleView) { 850 this.visibleView.detach(); 851 delete this.visibleView; 852 } 853 854 var view = new WebInspector.NetworkItemView(resource); 855 view.show(this._viewsContainerElement); 856 this.visibleView = view; 857 858 this.updateSidebarWidth(); 859 }, 860 861 _closeVisibleResource: function() 862 { 863 this.element.removeStyleClass("viewing-resource"); 864 865 if (this.visibleView) { 866 this.visibleView.detach(); 867 delete this.visibleView; 868 } 869 870 if (this._lastSelectedGraphTreeElement) 871 this._lastSelectedGraphTreeElement.select(true); 872 873 this.updateSidebarWidth(); 874 }, 875 876 _toggleLargerResources: function() 877 { 878 WebInspector.settings.resourcesLargeRows = !WebInspector.settings.resourcesLargeRows; 879 this._setLargerResources(WebInspector.settings.resourcesLargeRows); 880 }, 881 882 _setLargerResources: function(enabled) 883 { 884 this._largerResourcesButton.toggled = enabled; 885 if (!enabled) { 886 this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows."); 887 this._dataGrid.element.addStyleClass("small"); 888 this._timelineGrid.element.addStyleClass("small"); 889 this._viewsContainerElement.addStyleClass("small"); 890 } else { 891 this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); 892 this._dataGrid.element.removeStyleClass("small"); 893 this._timelineGrid.element.removeStyleClass("small"); 894 this._viewsContainerElement.removeStyleClass("small"); 895 } 896 this._updateOffscreenRows(); 897 }, 898 899 _getPopoverAnchor: function(element) 900 { 901 var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label"); 902 if (!anchor) 903 return null; 904 var resource = anchor.parentElement.resource; 905 return resource && resource.timing ? anchor : null; 906 }, 907 908 _showPopover: function(anchor) 909 { 910 var resource = anchor.parentElement.resource; 911 var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource); 912 var popover = new WebInspector.Popover(tableElement); 913 popover.show(anchor); 914 return popover; 915 }, 916 917 _toggleGridMode: function() 918 { 919 if (this._viewingResourceMode) { 920 this._viewingResourceMode = false; 921 this.element.removeStyleClass("viewing-resource"); 922 this._dataGrid.element.removeStyleClass("viewing-resource-mode"); 923 this._viewsContainerElement.addStyleClass("hidden"); 924 this.sidebarElement.style.right = 0; 925 this.sidebarElement.style.removeProperty("width"); 926 if (this._dataGrid.selectedNode) 927 this._dataGrid.selectedNode.selected = false; 928 } 929 930 if (this._briefGrid) { 931 this._dataGrid.element.removeStyleClass("full-grid-mode"); 932 this._dataGrid.element.addStyleClass("brief-grid-mode"); 933 934 this._dataGrid.hideColumn("method"); 935 this._dataGrid.hideColumn("status"); 936 this._dataGrid.hideColumn("type"); 937 this._dataGrid.hideColumn("size"); 938 this._dataGrid.hideColumn("time"); 939 940 var widths = {}; 941 widths.name = 20; 942 widths.timeline = 80; 943 } else { 944 this._dataGrid.element.addStyleClass("full-grid-mode"); 945 this._dataGrid.element.removeStyleClass("brief-grid-mode"); 946 947 this._dataGrid.showColumn("method"); 948 this._dataGrid.showColumn("status"); 949 this._dataGrid.showColumn("type"); 950 this._dataGrid.showColumn("size"); 951 this._dataGrid.showColumn("time"); 952 953 var widths = {}; 954 widths.name = 20; 955 widths.method = 7; 956 widths.status = 8; 957 widths.type = 10; 958 widths.size = 10; 959 widths.time = 10; 960 widths.timeline = 37; 961 } 962 963 this._dataGrid.showColumn("timeline"); 964 this._dataGrid.applyColumnWidthsMap(widths); 965 966 }, 967 968 _toggleViewingResourceMode: function() 969 { 970 if (this._viewingResourceMode) 971 return; 972 this._viewingResourceMode = true; 973 this._preservedColumnWidths = this._dataGrid.columnWidthsMap(); 974 975 this.element.addStyleClass("viewing-resource"); 976 this._dataGrid.element.addStyleClass("viewing-resource-mode"); 977 this._dataGrid.element.removeStyleClass("full-grid-mode"); 978 this._dataGrid.element.removeStyleClass("brief-grid-mode"); 979 980 this._dataGrid.hideColumn("method"); 981 this._dataGrid.hideColumn("status"); 982 this._dataGrid.hideColumn("type"); 983 this._dataGrid.hideColumn("size"); 984 this._dataGrid.hideColumn("time"); 985 this._dataGrid.hideColumn("timeline"); 986 987 this._viewsContainerElement.removeStyleClass("hidden"); 988 this.updateSidebarWidth(200); 989 990 var widths = {}; 991 widths.name = 100; 992 this._dataGrid.applyColumnWidthsMap(widths); 993 }, 994 995 _contextMenu: function(event) 996 { 997 var contextMenu = new WebInspector.ContextMenu(); 998 var gridNode = this._dataGrid.dataGridNodeFromNode(event.target); 999 var resource = gridNode && gridNode._resource; 1000 if (resource) 1001 contextMenu.appendItem(WebInspector.UIString("Copy entry as HAR"), this._exportResource.bind(this, resource)); 1002 contextMenu.appendItem(WebInspector.UIString("Copy network log as HAR"), this._exportAll.bind(this)); 1003 contextMenu.show(event); 1004 }, 1005 1006 _exportAll: function() 1007 { 1008 var harArchive = { 1009 log: (new WebInspector.HARLog()).build() 1010 } 1011 InspectorFrontendHost.copyText(JSON.stringify(harArchive)); 1012 }, 1013 1014 _exportResource: function(resource) 1015 { 1016 var har = (new WebInspector.HAREntry(resource)).build(); 1017 InspectorFrontendHost.copyText(JSON.stringify(har)); 1018 }, 1019 1020 _updateOffscreenRows: function(e) 1021 { 1022 var dataTableBody = this._dataGrid.dataTableBody; 1023 var rows = dataTableBody.children; 1024 var recordsCount = rows.length; 1025 if (recordsCount < 2) 1026 return; // Filler row only. 1027 1028 var visibleTop = this._dataGrid.scrollContainer.scrollTop; 1029 var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight; 1030 1031 var rowHeight = 0; 1032 1033 // Filler is at recordsCount - 1. 1034 var unfilteredRowIndex = 0; 1035 for (var i = 0; i < recordsCount - 1; ++i) { 1036 var row = rows[i]; 1037 1038 var dataGridNode = this._dataGrid.dataGridNodeFromNode(row); 1039 if (dataGridNode.isFilteredOut()) { 1040 row.removeStyleClass("offscreen"); 1041 continue; 1042 } 1043 1044 if (!rowHeight) 1045 rowHeight = row.offsetHeight; 1046 1047 var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop; 1048 if (rowIsVisible !== row.rowIsVisible) { 1049 if (rowIsVisible) 1050 row.removeStyleClass("offscreen"); 1051 else 1052 row.addStyleClass("offscreen"); 1053 row.rowIsVisible = rowIsVisible; 1054 } 1055 unfilteredRowIndex++; 1056 } 1057 } 1058 } 1059 1060 WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype; 1061 1062 WebInspector.NetworkBaseCalculator = function() 1063 { 1064 } 1065 1066 WebInspector.NetworkBaseCalculator.prototype = { 1067 computeSummaryValues: function(items) 1068 { 1069 var total = 0; 1070 var categoryValues = {}; 1071 1072 var itemsLength = items.length; 1073 for (var i = 0; i < itemsLength; ++i) { 1074 var item = items[i]; 1075 var value = this._value(item); 1076 if (typeof value === "undefined") 1077 continue; 1078 if (!(item.category.name in categoryValues)) 1079 categoryValues[item.category.name] = 0; 1080 categoryValues[item.category.name] += value; 1081 total += value; 1082 } 1083 1084 return {categoryValues: categoryValues, total: total}; 1085 }, 1086 1087 computeBarGraphPercentages: function(item) 1088 { 1089 return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100}; 1090 }, 1091 1092 computeBarGraphLabels: function(item) 1093 { 1094 const label = this.formatValue(this._value(item)); 1095 return {left: label, right: label, tooltip: label}; 1096 }, 1097 1098 get boundarySpan() 1099 { 1100 return this.maximumBoundary - this.minimumBoundary; 1101 }, 1102 1103 updateBoundaries: function(item) 1104 { 1105 this.minimumBoundary = 0; 1106 1107 var value = this._value(item); 1108 if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) { 1109 this.maximumBoundary = value; 1110 return true; 1111 } 1112 return false; 1113 }, 1114 1115 reset: function() 1116 { 1117 delete this.minimumBoundary; 1118 delete this.maximumBoundary; 1119 }, 1120 1121 _value: function(item) 1122 { 1123 return 0; 1124 }, 1125 1126 formatValue: function(value) 1127 { 1128 return value.toString(); 1129 } 1130 } 1131 1132 WebInspector.NetworkTimeCalculator = function(startAtZero) 1133 { 1134 WebInspector.NetworkBaseCalculator.call(this); 1135 this.startAtZero = startAtZero; 1136 } 1137 1138 WebInspector.NetworkTimeCalculator.prototype = { 1139 computeSummaryValues: function(resources) 1140 { 1141 var resourcesByCategory = {}; 1142 var resourcesLength = resources.length; 1143 for (var i = 0; i < resourcesLength; ++i) { 1144 var resource = resources[i]; 1145 if (!(resource.category.name in resourcesByCategory)) 1146 resourcesByCategory[resource.category.name] = []; 1147 resourcesByCategory[resource.category.name].push(resource); 1148 } 1149 1150 var earliestStart; 1151 var latestEnd; 1152 var categoryValues = {}; 1153 for (var category in resourcesByCategory) { 1154 resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime); 1155 categoryValues[category] = 0; 1156 1157 var segment = {start: -1, end: -1}; 1158 1159 var categoryResources = resourcesByCategory[category]; 1160 var resourcesLength = categoryResources.length; 1161 for (var i = 0; i < resourcesLength; ++i) { 1162 var resource = categoryResources[i]; 1163 if (resource.startTime === -1 || resource.endTime === -1) 1164 continue; 1165 1166 if (typeof earliestStart === "undefined") 1167 earliestStart = resource.startTime; 1168 else 1169 earliestStart = Math.min(earliestStart, resource.startTime); 1170 1171 if (typeof latestEnd === "undefined") 1172 latestEnd = resource.endTime; 1173 else 1174 latestEnd = Math.max(latestEnd, resource.endTime); 1175 1176 if (resource.startTime <= segment.end) { 1177 segment.end = Math.max(segment.end, resource.endTime); 1178 continue; 1179 } 1180 1181 categoryValues[category] += segment.end - segment.start; 1182 1183 segment.start = resource.startTime; 1184 segment.end = resource.endTime; 1185 } 1186 1187 // Add the last segment 1188 categoryValues[category] += segment.end - segment.start; 1189 } 1190 1191 return {categoryValues: categoryValues, total: latestEnd - earliestStart}; 1192 }, 1193 1194 computeBarGraphPercentages: function(resource) 1195 { 1196 if (resource.startTime !== -1) 1197 var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100; 1198 else 1199 var start = 0; 1200 1201 if (resource.responseReceivedTime !== -1) 1202 var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100; 1203 else 1204 var middle = (this.startAtZero ? start : 100); 1205 1206 if (resource.endTime !== -1) 1207 var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100; 1208 else 1209 var end = (this.startAtZero ? middle : 100); 1210 1211 if (this.startAtZero) { 1212 end -= start; 1213 middle -= start; 1214 start = 0; 1215 } 1216 1217 return {start: start, middle: middle, end: end}; 1218 }, 1219 1220 computePercentageFromEventTime: function(eventTime) 1221 { 1222 // This function computes a percentage in terms of the total loading time 1223 // of a specific event. If startAtZero is set, then this is useless, and we 1224 // want to return 0. 1225 if (eventTime !== -1 && !this.startAtZero) 1226 return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100; 1227 1228 return 0; 1229 }, 1230 1231 computeBarGraphLabels: function(resource) 1232 { 1233 var rightLabel = ""; 1234 if (resource.responseReceivedTime !== -1 && resource.endTime !== -1) 1235 rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime); 1236 1237 var hasLatency = resource.latency > 0; 1238 if (hasLatency) 1239 var leftLabel = this.formatValue(resource.latency); 1240 else 1241 var leftLabel = rightLabel; 1242 1243 if (resource.timing) 1244 return {left: leftLabel, right: rightLabel}; 1245 1246 if (hasLatency && rightLabel) { 1247 var total = this.formatValue(resource.duration); 1248 var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total); 1249 } else if (hasLatency) 1250 var tooltip = WebInspector.UIString("%s latency", leftLabel); 1251 else if (rightLabel) 1252 var tooltip = WebInspector.UIString("%s download", rightLabel); 1253 1254 if (resource.cached) 1255 tooltip = WebInspector.UIString("%s (from cache)", tooltip); 1256 return {left: leftLabel, right: rightLabel, tooltip: tooltip}; 1257 }, 1258 1259 updateBoundaries: function(resource) 1260 { 1261 var didChange = false; 1262 1263 var lowerBound; 1264 if (this.startAtZero) 1265 lowerBound = 0; 1266 else 1267 lowerBound = this._lowerBound(resource); 1268 1269 if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) { 1270 this.minimumBoundary = lowerBound; 1271 didChange = true; 1272 } 1273 1274 var upperBound = this._upperBound(resource); 1275 if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) { 1276 this.maximumBoundary = upperBound; 1277 didChange = true; 1278 } 1279 1280 return didChange; 1281 }, 1282 1283 formatValue: function(value) 1284 { 1285 return Number.secondsToString(value); 1286 }, 1287 1288 _lowerBound: function(resource) 1289 { 1290 return 0; 1291 }, 1292 1293 _upperBound: function(resource) 1294 { 1295 return 0; 1296 } 1297 } 1298 1299 WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype; 1300 1301 WebInspector.NetworkTransferTimeCalculator = function() 1302 { 1303 WebInspector.NetworkTimeCalculator.call(this, false); 1304 } 1305 1306 WebInspector.NetworkTransferTimeCalculator.prototype = { 1307 formatValue: function(value) 1308 { 1309 return Number.secondsToString(value); 1310 }, 1311 1312 _lowerBound: function(resource) 1313 { 1314 return resource.startTime; 1315 }, 1316 1317 _upperBound: function(resource) 1318 { 1319 return resource.endTime; 1320 } 1321 } 1322 1323 WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype; 1324 1325 WebInspector.NetworkTransferDurationCalculator = function() 1326 { 1327 WebInspector.NetworkTimeCalculator.call(this, true); 1328 } 1329 1330 WebInspector.NetworkTransferDurationCalculator.prototype = { 1331 formatValue: function(value) 1332 { 1333 return Number.secondsToString(value); 1334 }, 1335 1336 _upperBound: function(resource) 1337 { 1338 return resource.duration; 1339 } 1340 } 1341 1342 WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype; 1343 1344 WebInspector.NetworkDataGridNode = function(panel, resource) 1345 { 1346 WebInspector.DataGridNode.call(this, {}); 1347 this._panel = panel; 1348 this._resource = resource; 1349 } 1350 1351 WebInspector.NetworkDataGridNode.prototype = { 1352 createCells: function() 1353 { 1354 this._nameCell = this._createDivInTD("name"); 1355 this._methodCell = this._createDivInTD("method"); 1356 this._statusCell = this._createDivInTD("status"); 1357 this._typeCell = this._createDivInTD("type"); 1358 this._sizeCell = this._createDivInTD("size"); 1359 this._timeCell = this._createDivInTD("time"); 1360 this._createTimelineCell(); 1361 this._nameCell.addEventListener("click", this.select.bind(this), false); 1362 this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false); 1363 }, 1364 1365 isFilteredOut: function() 1366 { 1367 if (!this._panel._hiddenCategories.all) 1368 return false; 1369 return this._resource.category.name in this._panel._hiddenCategories; 1370 }, 1371 1372 select: function() 1373 { 1374 this._panel._showResource(this._resource); 1375 WebInspector.DataGridNode.prototype.select.apply(this, arguments); 1376 }, 1377 1378 _openInNewTab: function() 1379 { 1380 PageAgent.openInInspectedWindow(this._resource.url); 1381 }, 1382 1383 get selectable() 1384 { 1385 if (!this._panel._viewingResourceMode) 1386 return false; 1387 return !this.isFilteredOut(); 1388 }, 1389 1390 _createDivInTD: function(columnIdentifier) 1391 { 1392 var td = document.createElement("td"); 1393 td.className = columnIdentifier + "-column"; 1394 var div = document.createElement("div"); 1395 td.appendChild(div); 1396 this._element.appendChild(td); 1397 return div; 1398 }, 1399 1400 _createTimelineCell: function() 1401 { 1402 this._graphElement = document.createElement("div"); 1403 this._graphElement.className = "network-graph-side"; 1404 1405 this._barAreaElement = document.createElement("div"); 1406 // this._barAreaElement.className = "network-graph-bar-area hidden"; 1407 this._barAreaElement.className = "network-graph-bar-area"; 1408 this._barAreaElement.resource = this._resource; 1409 this._graphElement.appendChild(this._barAreaElement); 1410 1411 this._barLeftElement = document.createElement("div"); 1412 this._barLeftElement.className = "network-graph-bar waiting"; 1413 this._barAreaElement.appendChild(this._barLeftElement); 1414 1415 this._barRightElement = document.createElement("div"); 1416 this._barRightElement.className = "network-graph-bar"; 1417 this._barAreaElement.appendChild(this._barRightElement); 1418 1419 1420 this._labelLeftElement = document.createElement("div"); 1421 this._labelLeftElement.className = "network-graph-label waiting"; 1422 this._barAreaElement.appendChild(this._labelLeftElement); 1423 1424 this._labelRightElement = document.createElement("div"); 1425 this._labelRightElement.className = "network-graph-label"; 1426 this._barAreaElement.appendChild(this._labelRightElement); 1427 1428 this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false); 1429 1430 this._timelineCell = document.createElement("td"); 1431 this._timelineCell.className = "timeline-column"; 1432 this._element.appendChild(this._timelineCell); 1433 this._timelineCell.appendChild(this._graphElement); 1434 }, 1435 1436 refreshResource: function() 1437 { 1438 this._refreshNameCell(); 1439 1440 this._methodCell.textContent = this._resource.requestMethod; 1441 1442 this._refreshStatusCell(); 1443 1444 if (this._resource.mimeType) { 1445 this._typeCell.removeStyleClass("network-dim-cell"); 1446 this._typeCell.textContent = this._resource.mimeType; 1447 } else { 1448 this._typeCell.addStyleClass("network-dim-cell"); 1449 this._typeCell.textContent = WebInspector.UIString("Pending"); 1450 } 1451 1452 this._refreshSizeCell(); 1453 this._refreshTimeCell(); 1454 1455 if (this._resource.cached) 1456 this._graphElement.addStyleClass("resource-cached"); 1457 1458 this._element.addStyleClass("network-item"); 1459 if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) { 1460 this._element.removeMatchingStyleClasses("network-category-\\w+"); 1461 this._element.addStyleClass("network-category-" + this._resource.category.name); 1462 } 1463 }, 1464 1465 _refreshNameCell: function() 1466 { 1467 this._nameCell.removeChildren(); 1468 1469 if (this._resource.category === WebInspector.resourceCategories.images) { 1470 var previewImage = document.createElement("img"); 1471 previewImage.className = "image-network-icon-preview"; 1472 this._resource.populateImageSource(previewImage); 1473 1474 var iconElement = document.createElement("div"); 1475 iconElement.className = "icon"; 1476 iconElement.appendChild(previewImage); 1477 } else { 1478 var iconElement = document.createElement("img"); 1479 iconElement.className = "icon"; 1480 } 1481 this._nameCell.appendChild(iconElement); 1482 this._nameCell.appendChild(document.createTextNode(this._fileName())); 1483 1484 1485 var subtitle = this._resource.displayDomain; 1486 1487 if (this._resource.path && this._resource.lastPathComponent) { 1488 var lastPathComponentIndex = this._resource.path.lastIndexOf("/" + this._resource.lastPathComponent); 1489 if (lastPathComponentIndex != -1) 1490 subtitle += this._resource.path.substring(0, lastPathComponentIndex); 1491 } 1492 1493 this._appendSubtitle(this._nameCell, subtitle); 1494 this._nameCell.title = this._resource.url; 1495 }, 1496 1497 _fileName: function() 1498 { 1499 var fileName = this._resource.displayName; 1500 if (this._resource.queryString) 1501 fileName += "?" + this._resource.queryString; 1502 return fileName; 1503 }, 1504 1505 _refreshStatusCell: function() 1506 { 1507 this._statusCell.removeChildren(); 1508 1509 if (this._resource.failed) { 1510 if (this._resource.canceled) 1511 this._statusCell.textContent = WebInspector.UIString("(canceled)"); 1512 else 1513 this._statusCell.textContent = WebInspector.UIString("(failed)"); 1514 this._statusCell.addStyleClass("network-dim-cell"); 1515 return; 1516 } 1517 1518 var fromCache = this._resource.cached; 1519 if (fromCache) { 1520 this._statusCell.textContent = WebInspector.UIString("(from cache)"); 1521 this._statusCell.addStyleClass("network-dim-cell"); 1522 return; 1523 } 1524 1525 this._statusCell.removeStyleClass("network-dim-cell"); 1526 if (this._resource.statusCode) { 1527 this._statusCell.appendChild(document.createTextNode(this._resource.statusCode)); 1528 this._statusCell.removeStyleClass("network-dim-cell"); 1529 this._appendSubtitle(this._statusCell, this._resource.statusText); 1530 this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText; 1531 } else { 1532 if (this._resource.isDataURL() && this._resource.finished) 1533 this._statusCell.textContent = WebInspector.UIString("(data url)"); 1534 else 1535 this._statusCell.textContent = WebInspector.UIString("Pending"); 1536 this._statusCell.addStyleClass("network-dim-cell"); 1537 } 1538 }, 1539 1540 _refreshSizeCell: function() 1541 { 1542 var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?"; 1543 var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?"; 1544 var fromCache = this._resource.cached; 1545 this._sizeCell.textContent = !fromCache ? resourceSize : WebInspector.UIString("(from cache)"); 1546 if (fromCache) 1547 this._sizeCell.addStyleClass("network-dim-cell"); 1548 else 1549 this._sizeCell.removeStyleClass("network-dim-cell"); 1550 if (!fromCache) 1551 this._appendSubtitle(this._sizeCell, transferSize); 1552 }, 1553 1554 _refreshTimeCell: function() 1555 { 1556 if (this._resource.duration > 0) { 1557 this._timeCell.removeStyleClass("network-dim-cell"); 1558 this._timeCell.textContent = Number.secondsToString(this._resource.duration); 1559 this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency)); 1560 } else { 1561 this._timeCell.addStyleClass("network-dim-cell"); 1562 this._timeCell.textContent = WebInspector.UIString("Pending"); 1563 } 1564 }, 1565 1566 _appendSubtitle: function(cellElement, subtitleText) 1567 { 1568 var subtitleElement = document.createElement("div"); 1569 subtitleElement.className = "network-cell-subtitle"; 1570 subtitleElement.textContent = subtitleText; 1571 cellElement.appendChild(subtitleElement); 1572 }, 1573 1574 refreshGraph: function(calculator) 1575 { 1576 var percentages = calculator.computeBarGraphPercentages(this._resource); 1577 this._percentages = percentages; 1578 1579 this._barAreaElement.removeStyleClass("hidden"); 1580 1581 if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) { 1582 this._graphElement.removeMatchingStyleClasses("network-category-\\w+"); 1583 this._graphElement.addStyleClass("network-category-" + this._resource.category.name); 1584 } 1585 1586 this._barLeftElement.style.setProperty("left", percentages.start + "%"); 1587 this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); 1588 1589 this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); 1590 this._barRightElement.style.setProperty("left", percentages.middle + "%"); 1591 1592 var labels = calculator.computeBarGraphLabels(this._resource); 1593 this._labelLeftElement.textContent = labels.left; 1594 this._labelRightElement.textContent = labels.right; 1595 1596 var tooltip = (labels.tooltip || ""); 1597 this._barLeftElement.title = tooltip; 1598 this._labelLeftElement.title = tooltip; 1599 this._labelRightElement.title = tooltip; 1600 this._barRightElement.title = tooltip; 1601 }, 1602 1603 _refreshLabelPositions: function() 1604 { 1605 if (!this._percentages) 1606 return; 1607 this._labelLeftElement.style.removeProperty("left"); 1608 this._labelLeftElement.style.removeProperty("right"); 1609 this._labelLeftElement.removeStyleClass("before"); 1610 this._labelLeftElement.removeStyleClass("hidden"); 1611 1612 this._labelRightElement.style.removeProperty("left"); 1613 this._labelRightElement.style.removeProperty("right"); 1614 this._labelRightElement.removeStyleClass("after"); 1615 this._labelRightElement.removeStyleClass("hidden"); 1616 1617 const labelPadding = 10; 1618 const barRightElementOffsetWidth = this._barRightElement.offsetWidth; 1619 const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth; 1620 1621 if (this._barLeftElement) { 1622 var leftBarWidth = barLeftElementOffsetWidth - labelPadding; 1623 var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding; 1624 } else { 1625 var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding; 1626 var rightBarWidth = barRightElementOffsetWidth - labelPadding; 1627 } 1628 1629 const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth; 1630 const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth; 1631 1632 const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth); 1633 const labelAfter = (labelRightElementOffsetWidth > rightBarWidth); 1634 const graphElementOffsetWidth = this._graphElement.offsetWidth; 1635 1636 if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10)) 1637 var leftHidden = true; 1638 1639 if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10)) 1640 var rightHidden = true; 1641 1642 if (barLeftElementOffsetWidth == barRightElementOffsetWidth) { 1643 // The left/right label data are the same, so a before/after label can be replaced by an on-bar label. 1644 if (labelBefore && !labelAfter) 1645 leftHidden = true; 1646 else if (labelAfter && !labelBefore) 1647 rightHidden = true; 1648 } 1649 1650 if (labelBefore) { 1651 if (leftHidden) 1652 this._labelLeftElement.addStyleClass("hidden"); 1653 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%"); 1654 this._labelLeftElement.addStyleClass("before"); 1655 } else { 1656 this._labelLeftElement.style.setProperty("left", this._percentages.start + "%"); 1657 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%"); 1658 } 1659 1660 if (labelAfter) { 1661 if (rightHidden) 1662 this._labelRightElement.addStyleClass("hidden"); 1663 this._labelRightElement.style.setProperty("left", this._percentages.end + "%"); 1664 this._labelRightElement.addStyleClass("after"); 1665 } else { 1666 this._labelRightElement.style.setProperty("left", this._percentages.middle + "%"); 1667 this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%"); 1668 } 1669 } 1670 } 1671 1672 WebInspector.NetworkDataGridNode.NameComparator = function(a, b) 1673 { 1674 var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : ""); 1675 var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : ""); 1676 if (aFileName > bFileName) 1677 return 1; 1678 if (bFileName > aFileName) 1679 return -1; 1680 return 0; 1681 } 1682 1683 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b) 1684 { 1685 if (b._resource.cached && !a._resource.cached) 1686 return 1; 1687 if (a._resource.cached && !b._resource.cached) 1688 return -1; 1689 1690 if (a._resource.resourceSize === b._resource.resourceSize) 1691 return 0; 1692 1693 return a._resource.resourceSize - b._resource.resourceSize; 1694 } 1695 1696 WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b) 1697 { 1698 var aValue = a._resource[propertyName]; 1699 var bValue = b._resource[propertyName]; 1700 if (aValue > bValue) 1701 return revert ? -1 : 1; 1702 if (bValue > aValue) 1703 return revert ? 1 : -1; 1704 return 0; 1705 } 1706 1707 WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; 1708