1/* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30WebInspector.ResourcesPanel = function() 31{ 32 WebInspector.Panel.call(this); 33 34 this.element.addStyleClass("resources"); 35 36 this.filterBarElement = document.createElement("div"); 37 this.filterBarElement.id = "resources-filter"; 38 this.element.appendChild(this.filterBarElement); 39 40 this.viewsContainerElement = document.createElement("div"); 41 this.viewsContainerElement.id = "resource-views"; 42 this.element.appendChild(this.viewsContainerElement); 43 44 this.containerElement = document.createElement("div"); 45 this.containerElement.id = "resources-container"; 46 this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false); 47 this.element.appendChild(this.containerElement); 48 49 this.sidebarElement = document.createElement("div"); 50 this.sidebarElement.id = "resources-sidebar"; 51 this.sidebarElement.className = "sidebar"; 52 this.containerElement.appendChild(this.sidebarElement); 53 54 this.sidebarResizeElement = document.createElement("div"); 55 this.sidebarResizeElement.className = "sidebar-resizer-vertical"; 56 this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); 57 this.element.appendChild(this.sidebarResizeElement); 58 59 this.containerContentElement = document.createElement("div"); 60 this.containerContentElement.id = "resources-container-content"; 61 this.containerElement.appendChild(this.containerContentElement); 62 63 this.summaryElement = document.createElement("div"); 64 this.summaryElement.id = "resources-summary"; 65 this.containerContentElement.appendChild(this.summaryElement); 66 67 this.resourcesGraphsElement = document.createElement("div"); 68 this.resourcesGraphsElement.id = "resources-graphs"; 69 this.containerContentElement.appendChild(this.resourcesGraphsElement); 70 71 this.dividersElement = document.createElement("div"); 72 this.dividersElement.id = "resources-dividers"; 73 this.containerContentElement.appendChild(this.dividersElement); 74 75 this.dividersLabelBarElement = document.createElement("div"); 76 this.dividersLabelBarElement.id = "resources-dividers-label-bar"; 77 this.containerContentElement.appendChild(this.dividersLabelBarElement); 78 79 this.summaryGraphElement = document.createElement("canvas"); 80 this.summaryGraphElement.setAttribute("width", "450"); 81 this.summaryGraphElement.setAttribute("height", "38"); 82 this.summaryGraphElement.id = "resources-summary-graph"; 83 this.summaryElement.appendChild(this.summaryGraphElement); 84 85 this.legendElement = document.createElement("div"); 86 this.legendElement.id = "resources-graph-legend"; 87 this.summaryElement.appendChild(this.legendElement); 88 89 this.sidebarTreeElement = document.createElement("ol"); 90 this.sidebarTreeElement.className = "sidebar-tree"; 91 this.sidebarElement.appendChild(this.sidebarTreeElement); 92 93 this.sidebarTree = new TreeOutline(this.sidebarTreeElement); 94 95 var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time")); 96 timeGraphItem.onselect = this._graphSelected.bind(this); 97 98 var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator(); 99 var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator(); 100 101 timeGraphItem.sortingOptions = [ 102 { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator }, 103 { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator }, 104 { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator }, 105 { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator }, 106 { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator }, 107 ]; 108 109 timeGraphItem.selectedSortingOptionIndex = 1; 110 111 var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size")); 112 sizeGraphItem.onselect = this._graphSelected.bind(this); 113 114 var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator(); 115 sizeGraphItem.sortingOptions = [ 116 { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator }, 117 ]; 118 119 sizeGraphItem.selectedSortingOptionIndex = 0; 120 121 this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true); 122 this.sidebarTree.appendChild(this.graphsTreeElement); 123 124 this.graphsTreeElement.appendChild(timeGraphItem); 125 this.graphsTreeElement.appendChild(sizeGraphItem); 126 this.graphsTreeElement.expand(); 127 128 this.resourcesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true); 129 this.sidebarTree.appendChild(this.resourcesTreeElement); 130 131 this.resourcesTreeElement.expand(); 132 133 var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel."); 134 var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower."); 135 var panelEnablerButton = WebInspector.UIString("Enable resource tracking"); 136 137 this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); 138 this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this); 139 140 this.element.appendChild(this.panelEnablerView.element); 141 142 this.enableToggleButton = this.createStatusBarButton(); 143 this.enableToggleButton.className = "enable-toggle-status-bar-item status-bar-item"; 144 this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false); 145 146 this.largerResourcesButton = this.createStatusBarButton(); 147 this.largerResourcesButton.id = "resources-larger-resources-status-bar-item"; 148 this.largerResourcesButton.className = "status-bar-item toggled-on"; 149 this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); 150 this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false); 151 152 this.sortingSelectElement = document.createElement("select"); 153 this.sortingSelectElement.className = "status-bar-item"; 154 this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false); 155 156 var createFilterElement = function (category) { 157 var categoryElement = document.createElement("li"); 158 categoryElement.category = category; 159 categoryElement.addStyleClass(category); 160 var label = WebInspector.UIString("All"); 161 if (WebInspector.resourceCategories[category]) 162 label = WebInspector.resourceCategories[category].title; 163 categoryElement.appendChild(document.createTextNode(label)); 164 categoryElement.addEventListener("click", this._updateFilter.bind(this), false); 165 this.filterBarElement.appendChild(categoryElement); 166 return categoryElement; 167 }; 168 169 var allElement = createFilterElement.call(this, "all"); 170 this.filter(allElement.category); 171 for (var i = 0; i < this.categoryOrder.length; i++) 172 createFilterElement.call(this, this.categoryOrder[i]); 173 174 this.reset(); 175 176 timeGraphItem.select(); 177} 178 179WebInspector.ResourcesPanel.prototype = { 180 toolbarItemClass: "resources", 181 182 categoryOrder: ["documents", "stylesheets", "images", "scripts", "xhr", "fonts", "other"], 183 184 filter: function (category) { 185 if (this._filterCategory && this._filterCategory === category) 186 return; 187 188 if (this._filterCategory) { 189 var filterElement = this.filterBarElement.getElementsByClassName(this._filterCategory)[0]; 190 filterElement.removeStyleClass("selected"); 191 var oldClass = "filter-" + this._filterCategory; 192 this.resourcesTreeElement.childrenListElement.removeStyleClass(oldClass); 193 this.resourcesGraphsElement.removeStyleClass(oldClass); 194 } 195 this._filterCategory = category; 196 var filterElement = this.filterBarElement.getElementsByClassName(this._filterCategory)[0]; 197 filterElement.addStyleClass("selected"); 198 var newClass = "filter-" + this._filterCategory; 199 this.resourcesTreeElement.childrenListElement.addStyleClass(newClass); 200 this.resourcesGraphsElement.addStyleClass(newClass); 201 }, 202 203 _updateFilter: function (e) { 204 this.filter(e.target.category); 205 }, 206 207 get toolbarItemLabel() 208 { 209 return WebInspector.UIString("Resources"); 210 }, 211 212 get statusBarItems() 213 { 214 return [this.enableToggleButton, this.largerResourcesButton, this.sortingSelectElement]; 215 }, 216 217 show: function() 218 { 219 WebInspector.Panel.prototype.show.call(this); 220 221 this._updateDividersLabelBarPosition(); 222 this._updateSidebarWidth(); 223 this.refreshIfNeeded(); 224 225 var visibleView = this.visibleView; 226 if (visibleView) { 227 visibleView.headersVisible = true; 228 visibleView.show(this.viewsContainerElement); 229 } 230 231 // Hide any views that are visible that are not this panel's current visible view. 232 // This can happen when a ResourceView is visible in the Scripts panel then switched 233 // to the this panel. 234 var resourcesLength = this._resources.length; 235 for (var i = 0; i < resourcesLength; ++i) { 236 var resource = this._resources[i]; 237 var view = resource._resourcesView; 238 if (!view || view === visibleView) 239 continue; 240 view.visible = false; 241 } 242 }, 243 244 resize: function() 245 { 246 this._updateGraphDividersIfNeeded(); 247 248 var visibleView = this.visibleView; 249 if (visibleView && "resize" in visibleView) 250 visibleView.resize(); 251 }, 252 253 get searchableViews() 254 { 255 var views = []; 256 257 const visibleView = this.visibleView; 258 if (visibleView && visibleView.performSearch) 259 views.push(visibleView); 260 261 var resourcesLength = this._resources.length; 262 for (var i = 0; i < resourcesLength; ++i) { 263 var resource = this._resources[i]; 264 if (!resource._resourcesTreeElement) 265 continue; 266 var resourceView = this.resourceViewForResource(resource); 267 if (!resourceView.performSearch || resourceView === visibleView) 268 continue; 269 views.push(resourceView); 270 } 271 272 return views; 273 }, 274 275 get searchResultsSortFunction() 276 { 277 const resourceTreeElementSortFunction = this.sortingFunction; 278 279 function sortFuction(a, b) 280 { 281 return resourceTreeElementSortFunction(a.resource._resourcesTreeElement, b.resource._resourcesTreeElement); 282 } 283 284 return sortFuction; 285 }, 286 287 searchMatchFound: function(view, matches) 288 { 289 view.resource._resourcesTreeElement.searchMatches = matches; 290 }, 291 292 searchCanceled: function(startingNewSearch) 293 { 294 WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); 295 296 if (startingNewSearch || !this._resources) 297 return; 298 299 for (var i = 0; i < this._resources.length; ++i) { 300 var resource = this._resources[i]; 301 if (resource._resourcesTreeElement) 302 resource._resourcesTreeElement.updateErrorsAndWarnings(); 303 } 304 }, 305 306 performSearch: function(query) 307 { 308 for (var i = 0; i < this._resources.length; ++i) { 309 var resource = this._resources[i]; 310 if (resource._resourcesTreeElement) 311 resource._resourcesTreeElement.resetBubble(); 312 } 313 314 WebInspector.Panel.prototype.performSearch.call(this, query); 315 }, 316 317 get visibleView() 318 { 319 if (this.visibleResource) 320 return this.visibleResource._resourcesView; 321 return null; 322 }, 323 324 get calculator() 325 { 326 return this._calculator; 327 }, 328 329 set calculator(x) 330 { 331 if (!x || this._calculator === x) 332 return; 333 334 this._calculator = x; 335 this._calculator.reset(); 336 337 this._staleResources = this._resources; 338 this.refresh(); 339 }, 340 341 get sortingFunction() 342 { 343 return this._sortingFunction; 344 }, 345 346 set sortingFunction(x) 347 { 348 this._sortingFunction = x; 349 this._sortResourcesIfNeeded(); 350 }, 351 352 get needsRefresh() 353 { 354 return this._needsRefresh; 355 }, 356 357 set needsRefresh(x) 358 { 359 if (this._needsRefresh === x) 360 return; 361 362 this._needsRefresh = x; 363 364 if (x) { 365 if (this.visible && !("_refreshTimeout" in this)) 366 this._refreshTimeout = setTimeout(this.refresh.bind(this), 500); 367 } else { 368 if ("_refreshTimeout" in this) { 369 clearTimeout(this._refreshTimeout); 370 delete this._refreshTimeout; 371 } 372 } 373 }, 374 375 refreshIfNeeded: function() 376 { 377 if (this.needsRefresh) 378 this.refresh(); 379 }, 380 381 refresh: function() 382 { 383 this.needsRefresh = false; 384 385 var staleResourcesLength = this._staleResources.length; 386 var boundariesChanged = false; 387 388 for (var i = 0; i < staleResourcesLength; ++i) { 389 var resource = this._staleResources[i]; 390 if (!resource._resourcesTreeElement) { 391 // Create the resource tree element and graph. 392 resource._resourcesTreeElement = new WebInspector.ResourceSidebarTreeElement(resource); 393 resource._resourcesTreeElement._resourceGraph = new WebInspector.ResourceGraph(resource); 394 395 this.resourcesTreeElement.appendChild(resource._resourcesTreeElement); 396 this.resourcesGraphsElement.appendChild(resource._resourcesTreeElement._resourceGraph.graphElement); 397 } 398 399 resource._resourcesTreeElement.refresh(); 400 401 if (this.calculator.updateBoundaries(resource)) 402 boundariesChanged = true; 403 } 404 405 if (boundariesChanged) { 406 // The boundaries changed, so all resource graphs are stale. 407 this._staleResources = this._resources; 408 staleResourcesLength = this._staleResources.length; 409 } 410 411 for (var i = 0; i < staleResourcesLength; ++i) 412 this._staleResources[i]._resourcesTreeElement._resourceGraph.refresh(this.calculator); 413 414 this._staleResources = []; 415 416 this._updateGraphDividersIfNeeded(); 417 this._sortResourcesIfNeeded(); 418 this._updateSummaryGraph(); 419 }, 420 421 resourceTrackingWasEnabled: function() 422 { 423 this.reset(); 424 }, 425 426 resourceTrackingWasDisabled: function() 427 { 428 this.reset(); 429 }, 430 431 reset: function() 432 { 433 this.closeVisibleResource(); 434 435 this.containerElement.scrollTop = 0; 436 437 delete this.currentQuery; 438 this.searchCanceled(); 439 440 if (this._calculator) 441 this._calculator.reset(); 442 443 if (this._resources) { 444 var resourcesLength = this._resources.length; 445 for (var i = 0; i < resourcesLength; ++i) { 446 var resource = this._resources[i]; 447 448 resource.warnings = 0; 449 resource.errors = 0; 450 451 delete resource._resourcesTreeElement; 452 delete resource._resourcesView; 453 } 454 } 455 456 this._resources = []; 457 this._staleResources = []; 458 459 this.resourcesTreeElement.removeChildren(); 460 this.viewsContainerElement.removeChildren(); 461 this.resourcesGraphsElement.removeChildren(); 462 this.legendElement.removeChildren(); 463 464 this._updateGraphDividersIfNeeded(true); 465 466 this._drawSummaryGraph(); // draws an empty graph 467 468 if (InspectorController.resourceTrackingEnabled()) { 469 this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable."); 470 this.enableToggleButton.addStyleClass("toggled-on"); 471 this.largerResourcesButton.removeStyleClass("hidden"); 472 this.sortingSelectElement.removeStyleClass("hidden"); 473 this.panelEnablerView.visible = false; 474 } else { 475 this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable."); 476 this.enableToggleButton.removeStyleClass("toggled-on"); 477 this.largerResourcesButton.addStyleClass("hidden"); 478 this.sortingSelectElement.addStyleClass("hidden"); 479 this.panelEnablerView.visible = true; 480 } 481 }, 482 483 addResource: function(resource) 484 { 485 this._resources.push(resource); 486 this.refreshResource(resource); 487 }, 488 489 removeResource: function(resource) 490 { 491 if (this.visibleView === resource._resourcesView) 492 this.closeVisibleResource(); 493 494 this._resources.remove(resource, true); 495 496 if (resource._resourcesTreeElement) { 497 this.resourcesTreeElement.removeChild(resource._resourcesTreeElement); 498 this.resourcesGraphsElement.removeChild(resource._resourcesTreeElement._resourceGraph.graphElement); 499 } 500 501 resource.warnings = 0; 502 resource.errors = 0; 503 504 delete resource._resourcesTreeElement; 505 delete resource._resourcesView; 506 507 this._adjustScrollPosition(); 508 }, 509 510 addMessageToResource: function(resource, msg) 511 { 512 if (!resource) 513 return; 514 515 switch (msg.level) { 516 case WebInspector.ConsoleMessage.MessageLevel.Warning: 517 resource.warnings += msg.repeatDelta; 518 break; 519 case WebInspector.ConsoleMessage.MessageLevel.Error: 520 resource.errors += msg.repeatDelta; 521 break; 522 } 523 524 if (!this.currentQuery && resource._resourcesTreeElement) 525 resource._resourcesTreeElement.updateErrorsAndWarnings(); 526 527 var view = this.resourceViewForResource(resource); 528 if (view.addMessage) 529 view.addMessage(msg); 530 }, 531 532 clearMessages: function() 533 { 534 var resourcesLength = this._resources.length; 535 for (var i = 0; i < resourcesLength; ++i) { 536 var resource = this._resources[i]; 537 resource.warnings = 0; 538 resource.errors = 0; 539 540 if (!this.currentQuery && resource._resourcesTreeElement) 541 resource._resourcesTreeElement.updateErrorsAndWarnings(); 542 543 var view = resource._resourcesView; 544 if (!view || !view.clearMessages) 545 continue; 546 view.clearMessages(); 547 } 548 }, 549 550 refreshResource: function(resource) 551 { 552 this._staleResources.push(resource); 553 this.needsRefresh = true; 554 }, 555 556 recreateViewForResourceIfNeeded: function(resource) 557 { 558 if (!resource || !resource._resourcesView) 559 return; 560 561 var newView = this._createResourceView(resource); 562 if (newView.prototype === resource._resourcesView.prototype) 563 return; 564 565 resource.warnings = 0; 566 resource.errors = 0; 567 568 if (!this.currentQuery && resource._resourcesTreeElement) 569 resource._resourcesTreeElement.updateErrorsAndWarnings(); 570 571 var oldView = resource._resourcesView; 572 573 resource._resourcesView.detach(); 574 delete resource._resourcesView; 575 576 resource._resourcesView = newView; 577 578 newView.headersVisible = oldView.headersVisible; 579 580 if (oldView.visible && oldView.element.parentNode) 581 newView.show(oldView.element.parentNode); 582 }, 583 584 showResource: function(resource, line) 585 { 586 if (!resource) 587 return; 588 589 this.containerElement.addStyleClass("viewing-resource"); 590 591 if (this.visibleResource && this.visibleResource._resourcesView) 592 this.visibleResource._resourcesView.hide(); 593 594 var view = this.resourceViewForResource(resource); 595 view.headersVisible = true; 596 view.show(this.viewsContainerElement); 597 598 if (line) { 599 if (view.revealLine) 600 view.revealLine(line); 601 if (view.highlightLine) 602 view.highlightLine(line); 603 } 604 605 if (resource._resourcesTreeElement) { 606 resource._resourcesTreeElement.reveal(); 607 resource._resourcesTreeElement.select(true); 608 } 609 610 this.visibleResource = resource; 611 612 this._updateSidebarWidth(); 613 }, 614 615 showView: function(view) 616 { 617 if (!view) 618 return; 619 this.showResource(view.resource); 620 }, 621 622 closeVisibleResource: function() 623 { 624 this.containerElement.removeStyleClass("viewing-resource"); 625 this._updateDividersLabelBarPosition(); 626 627 if (this.visibleResource && this.visibleResource._resourcesView) 628 this.visibleResource._resourcesView.hide(); 629 delete this.visibleResource; 630 631 if (this._lastSelectedGraphTreeElement) 632 this._lastSelectedGraphTreeElement.select(true); 633 634 this._updateSidebarWidth(); 635 }, 636 637 resourceViewForResource: function(resource) 638 { 639 if (!resource) 640 return null; 641 if (!resource._resourcesView) 642 resource._resourcesView = this._createResourceView(resource); 643 return resource._resourcesView; 644 }, 645 646 sourceFrameForResource: function(resource) 647 { 648 var view = this.resourceViewForResource(resource); 649 if (!view) 650 return null; 651 652 if (!view.setupSourceFrameIfNeeded) 653 return null; 654 655 // Setting up the source frame requires that we be attached. 656 if (!this.element.parentNode) 657 this.attach(); 658 659 view.setupSourceFrameIfNeeded(); 660 return view.sourceFrame; 661 }, 662 663 handleKeyEvent: function(event) 664 { 665 this.sidebarTree.handleKeyEvent(event); 666 }, 667 668 _makeLegendElement: function(label, value, color) 669 { 670 var legendElement = document.createElement("label"); 671 legendElement.className = "resources-graph-legend-item"; 672 673 if (color) { 674 var swatch = document.createElement("canvas"); 675 swatch.className = "resources-graph-legend-swatch"; 676 swatch.setAttribute("width", "13"); 677 swatch.setAttribute("height", "24"); 678 679 legendElement.appendChild(swatch); 680 681 this._drawSwatch(swatch, color); 682 } 683 684 var labelElement = document.createElement("div"); 685 labelElement.className = "resources-graph-legend-label"; 686 legendElement.appendChild(labelElement); 687 688 var headerElement = document.createElement("div"); 689 var headerElement = document.createElement("div"); 690 headerElement.className = "resources-graph-legend-header"; 691 headerElement.textContent = label; 692 labelElement.appendChild(headerElement); 693 694 var valueElement = document.createElement("div"); 695 valueElement.className = "resources-graph-legend-value"; 696 valueElement.textContent = value; 697 labelElement.appendChild(valueElement); 698 699 return legendElement; 700 }, 701 702 _sortResourcesIfNeeded: function() 703 { 704 var sortedElements = [].concat(this.resourcesTreeElement.children); 705 sortedElements.sort(this.sortingFunction); 706 707 var sortedElementsLength = sortedElements.length; 708 for (var i = 0; i < sortedElementsLength; ++i) { 709 var treeElement = sortedElements[i]; 710 if (treeElement === this.resourcesTreeElement.children[i]) 711 continue; 712 713 var wasSelected = treeElement.selected; 714 this.resourcesTreeElement.removeChild(treeElement); 715 this.resourcesTreeElement.insertChild(treeElement, i); 716 if (wasSelected) 717 treeElement.select(true); 718 719 var graphElement = treeElement._resourceGraph.graphElement; 720 this.resourcesGraphsElement.insertBefore(graphElement, this.resourcesGraphsElement.children[i]); 721 } 722 }, 723 724 _updateGraphDividersIfNeeded: function(force) 725 { 726 if (!this.visible) { 727 this.needsRefresh = true; 728 return; 729 } 730 731 if (document.body.offsetWidth <= 0) { 732 // The stylesheet hasn't loaded yet or the window is closed, 733 // so we can't calculate what is need. Return early. 734 return; 735 } 736 737 var dividerCount = Math.round(this.dividersElement.offsetWidth / 64); 738 var slice = this.calculator.boundarySpan / dividerCount; 739 if (!force && this._currentDividerSlice === slice) 740 return; 741 742 this._currentDividerSlice = slice; 743 744 this.dividersElement.removeChildren(); 745 this.dividersLabelBarElement.removeChildren(); 746 747 for (var i = 1; i <= dividerCount; ++i) { 748 var divider = document.createElement("div"); 749 divider.className = "resources-divider"; 750 if (i === dividerCount) 751 divider.addStyleClass("last"); 752 divider.style.left = ((i / dividerCount) * 100) + "%"; 753 754 this.dividersElement.appendChild(divider.cloneNode()); 755 756 var label = document.createElement("div"); 757 label.className = "resources-divider-label"; 758 if (!isNaN(slice)) 759 label.textContent = this.calculator.formatValue(slice * i); 760 divider.appendChild(label); 761 762 this.dividersLabelBarElement.appendChild(divider); 763 } 764 }, 765 766 _fadeOutRect: function(ctx, x, y, w, h, a1, a2) 767 { 768 ctx.save(); 769 770 var gradient = ctx.createLinearGradient(x, y, x, y + h); 771 gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")"); 772 gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")"); 773 gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)"); 774 775 ctx.globalCompositeOperation = "destination-out"; 776 777 ctx.fillStyle = gradient; 778 ctx.fillRect(x, y, w, h); 779 780 ctx.restore(); 781 }, 782 783 _drawSwatch: function(canvas, color) 784 { 785 var ctx = canvas.getContext("2d"); 786 787 function drawSwatchSquare() { 788 ctx.fillStyle = color; 789 ctx.fillRect(0, 0, 13, 13); 790 791 var gradient = ctx.createLinearGradient(0, 0, 13, 13); 792 gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)"); 793 gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); 794 795 ctx.fillStyle = gradient; 796 ctx.fillRect(0, 0, 13, 13); 797 798 gradient = ctx.createLinearGradient(13, 13, 0, 0); 799 gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)"); 800 gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)"); 801 802 ctx.fillStyle = gradient; 803 ctx.fillRect(0, 0, 13, 13); 804 805 ctx.strokeStyle = "rgba(0, 0, 0, 0.6)"; 806 ctx.strokeRect(0.5, 0.5, 12, 12); 807 } 808 809 ctx.clearRect(0, 0, 13, 24); 810 811 drawSwatchSquare(); 812 813 ctx.save(); 814 815 ctx.translate(0, 25); 816 ctx.scale(1, -1); 817 818 drawSwatchSquare(); 819 820 ctx.restore(); 821 822 this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0); 823 }, 824 825 _drawSummaryGraph: function(segments) 826 { 827 if (!this.summaryGraphElement) 828 return; 829 830 if (!segments || !segments.length) { 831 segments = [{color: "white", value: 1}]; 832 this._showingEmptySummaryGraph = true; 833 } else 834 delete this._showingEmptySummaryGraph; 835 836 // Calculate the total of all segments. 837 var total = 0; 838 for (var i = 0; i < segments.length; ++i) 839 total += segments[i].value; 840 841 // Calculate the percentage of each segment, rounded to the nearest percent. 842 var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) }); 843 844 // Calculate the total percentage. 845 var percentTotal = 0; 846 for (var i = 0; i < percents.length; ++i) 847 percentTotal += percents[i]; 848 849 // Make sure our percentage total is not greater-than 100, it can be greater 850 // if we rounded up for a few segments. 851 while (percentTotal > 100) { 852 for (var i = 0; i < percents.length && percentTotal > 100; ++i) { 853 if (percents[i] > 1) { 854 --percents[i]; 855 --percentTotal; 856 } 857 } 858 } 859 860 // Make sure our percentage total is not less-than 100, it can be less 861 // if we rounded down for a few segments. 862 while (percentTotal < 100) { 863 for (var i = 0; i < percents.length && percentTotal < 100; ++i) { 864 ++percents[i]; 865 ++percentTotal; 866 } 867 } 868 869 var ctx = this.summaryGraphElement.getContext("2d"); 870 871 var x = 0; 872 var y = 0; 873 var w = 450; 874 var h = 19; 875 var r = (h / 2); 876 877 function drawPillShadow() 878 { 879 // This draws a line with a shadow that is offset away from the line. The line is stroked 880 // twice with different X shadow offsets to give more feathered edges. Later we erase the 881 // line with destination-out 100% transparent black, leaving only the shadow. This only 882 // works if nothing has been drawn into the canvas yet. 883 884 ctx.beginPath(); 885 ctx.moveTo(x + 4, y + h - 3 - 0.5); 886 ctx.lineTo(x + w - 4, y + h - 3 - 0.5); 887 ctx.closePath(); 888 889 ctx.save(); 890 891 ctx.shadowBlur = 2; 892 ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; 893 ctx.shadowOffsetX = 3; 894 ctx.shadowOffsetY = 5; 895 896 ctx.strokeStyle = "white"; 897 ctx.lineWidth = 1; 898 899 ctx.stroke(); 900 901 ctx.shadowOffsetX = -3; 902 903 ctx.stroke(); 904 905 ctx.restore(); 906 907 ctx.save(); 908 909 ctx.globalCompositeOperation = "destination-out"; 910 ctx.strokeStyle = "rgba(0, 0, 0, 1)"; 911 ctx.lineWidth = 1; 912 913 ctx.stroke(); 914 915 ctx.restore(); 916 } 917 918 function drawPill() 919 { 920 // Make a rounded rect path. 921 ctx.beginPath(); 922 ctx.moveTo(x, y + r); 923 ctx.lineTo(x, y + h - r); 924 ctx.quadraticCurveTo(x, y + h, x + r, y + h); 925 ctx.lineTo(x + w - r, y + h); 926 ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r); 927 ctx.lineTo(x + w, y + r); 928 ctx.quadraticCurveTo(x + w, y, x + w - r, y); 929 ctx.lineTo(x + r, y); 930 ctx.quadraticCurveTo(x, y, x, y + r); 931 ctx.closePath(); 932 933 // Clip to the rounded rect path. 934 ctx.save(); 935 ctx.clip(); 936 937 // Fill the segments with the associated color. 938 var previousSegmentsWidth = 0; 939 for (var i = 0; i < segments.length; ++i) { 940 var segmentWidth = Math.round(w * percents[i] / 100); 941 ctx.fillStyle = segments[i].color; 942 ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h); 943 previousSegmentsWidth += segmentWidth; 944 } 945 946 // Draw the segment divider lines. 947 ctx.lineWidth = 1; 948 for (var i = 1; i < 20; ++i) { 949 ctx.beginPath(); 950 ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y); 951 ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h); 952 ctx.closePath(); 953 954 ctx.strokeStyle = "rgba(0, 0, 0, 0.2)"; 955 ctx.stroke(); 956 957 ctx.beginPath(); 958 ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y); 959 ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h); 960 ctx.closePath(); 961 962 ctx.strokeStyle = "rgba(255, 255, 255, 0.2)"; 963 ctx.stroke(); 964 } 965 966 // Draw the pill shading. 967 var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5)); 968 lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)"); 969 lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)"); 970 lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); 971 972 var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h); 973 darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)"); 974 darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)"); 975 darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)"); 976 977 ctx.fillStyle = darkGradient; 978 ctx.fillRect(x, y, w, h); 979 980 ctx.fillStyle = lightGradient; 981 ctx.fillRect(x, y, w, h); 982 983 ctx.restore(); 984 } 985 986 ctx.clearRect(x, y, w, (h * 2)); 987 988 drawPillShadow(); 989 drawPill(); 990 991 ctx.save(); 992 993 ctx.translate(0, (h * 2) + 1); 994 ctx.scale(1, -1); 995 996 drawPill(); 997 998 ctx.restore(); 999 1000 this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0); 1001 }, 1002 1003 _updateSummaryGraph: function() 1004 { 1005 var graphInfo = this.calculator.computeSummaryValues(this._resources); 1006 1007 var categoryColors = {documents: {r: 47, g: 102, b: 236}, stylesheets: {r: 157, g: 231, b: 119}, images: {r: 164, g: 60, b: 255}, scripts: {r: 255, g: 121, b: 0}, xhr: {r: 231, g: 231, b: 10}, fonts: {r: 255, g: 82, b: 62}, other: {r: 186, g: 186, b: 186}}; 1008 var fillSegments = []; 1009 1010 this.legendElement.removeChildren(); 1011 1012 for (var i = 0; i < this.categoryOrder.length; ++i) { 1013 var category = this.categoryOrder[i]; 1014 var size = graphInfo.categoryValues[category]; 1015 if (!size) 1016 continue; 1017 1018 var color = categoryColors[category]; 1019 var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")"; 1020 1021 var fillSegment = {color: colorString, value: size}; 1022 fillSegments.push(fillSegment); 1023 1024 var legendLabel = this._makeLegendElement(WebInspector.resourceCategories[category].title, this.calculator.formatValue(size), colorString); 1025 this.legendElement.appendChild(legendLabel); 1026 } 1027 1028 if (graphInfo.total) { 1029 var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total)); 1030 totalLegendLabel.addStyleClass("total"); 1031 this.legendElement.appendChild(totalLegendLabel); 1032 } 1033 1034 this._drawSummaryGraph(fillSegments); 1035 }, 1036 1037 _updateDividersLabelBarPosition: function() 1038 { 1039 var scrollTop = this.containerElement.scrollTop; 1040 var dividersTop = (scrollTop < this.summaryElement.offsetHeight ? this.summaryElement.offsetHeight : scrollTop); 1041 this.dividersElement.style.top = scrollTop + "px"; 1042 this.dividersLabelBarElement.style.top = dividersTop + "px"; 1043 }, 1044 1045 _graphSelected: function(treeElement) 1046 { 1047 if (this._lastSelectedGraphTreeElement) 1048 this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex; 1049 1050 this._lastSelectedGraphTreeElement = treeElement; 1051 1052 this.sortingSelectElement.removeChildren(); 1053 for (var i = 0; i < treeElement.sortingOptions.length; ++i) { 1054 var sortingOption = treeElement.sortingOptions[i]; 1055 var option = document.createElement("option"); 1056 option.label = sortingOption.name; 1057 option.sortingFunction = sortingOption.sortingFunction; 1058 option.calculator = sortingOption.calculator; 1059 this.sortingSelectElement.appendChild(option); 1060 } 1061 1062 this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex; 1063 this._changeSortingFunction(); 1064 1065 this.closeVisibleResource(); 1066 this.containerElement.scrollTop = 0; 1067 }, 1068 1069 _toggleLargerResources: function() 1070 { 1071 if (!this.resourcesTreeElement._childrenListNode) 1072 return; 1073 1074 this.resourcesTreeElement.smallChildren = !this.resourcesTreeElement.smallChildren; 1075 1076 if (this.resourcesTreeElement.smallChildren) { 1077 this.resourcesGraphsElement.addStyleClass("small"); 1078 this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows."); 1079 this.largerResourcesButton.removeStyleClass("toggled-on"); 1080 this._adjustScrollPosition(); 1081 } else { 1082 this.resourcesGraphsElement.removeStyleClass("small"); 1083 this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); 1084 this.largerResourcesButton.addStyleClass("toggled-on"); 1085 } 1086 }, 1087 1088 _adjustScrollPosition: function() 1089 { 1090 // Prevent the container from being scrolled off the end. 1091 if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight) 1092 this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight); 1093 }, 1094 1095 _changeSortingFunction: function() 1096 { 1097 var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex]; 1098 this.sortingFunction = selectedOption.sortingFunction; 1099 this.calculator = selectedOption.calculator; 1100 }, 1101 1102 _createResourceView: function(resource) 1103 { 1104 switch (resource.category) { 1105 case WebInspector.resourceCategories.documents: 1106 case WebInspector.resourceCategories.stylesheets: 1107 case WebInspector.resourceCategories.scripts: 1108 case WebInspector.resourceCategories.xhr: 1109 return new WebInspector.SourceView(resource); 1110 case WebInspector.resourceCategories.images: 1111 return new WebInspector.ImageView(resource); 1112 case WebInspector.resourceCategories.fonts: 1113 return new WebInspector.FontView(resource); 1114 default: 1115 return new WebInspector.ResourceView(resource); 1116 } 1117 }, 1118 1119 _startSidebarDragging: function(event) 1120 { 1121 WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); 1122 }, 1123 1124 _sidebarDragging: function(event) 1125 { 1126 this._updateSidebarWidth(event.pageX); 1127 1128 event.preventDefault(); 1129 }, 1130 1131 _endSidebarDragging: function(event) 1132 { 1133 WebInspector.elementDragEnd(event); 1134 }, 1135 1136 _updateSidebarWidth: function(width) 1137 { 1138 if (this.sidebarElement.offsetWidth <= 0) { 1139 // The stylesheet hasn't loaded yet or the window is closed, 1140 // so we can't calculate what is need. Return early. 1141 return; 1142 } 1143 1144 if (!("_currentSidebarWidth" in this)) 1145 this._currentSidebarWidth = this.sidebarElement.offsetWidth; 1146 1147 if (typeof width === "undefined") 1148 width = this._currentSidebarWidth; 1149 1150 width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); 1151 1152 this._currentSidebarWidth = width; 1153 1154 if (this.visibleResource) { 1155 this.containerElement.style.width = width + "px"; 1156 this.sidebarElement.style.removeProperty("width"); 1157 } else { 1158 this.sidebarElement.style.width = width + "px"; 1159 this.containerElement.style.removeProperty("width"); 1160 } 1161 1162 this.containerContentElement.style.left = width + "px"; 1163 this.viewsContainerElement.style.left = width + "px"; 1164 this.sidebarResizeElement.style.left = (width - 3) + "px"; 1165 1166 this._updateGraphDividersIfNeeded(); 1167 1168 var visibleView = this.visibleView; 1169 if (visibleView && "resize" in visibleView) 1170 visibleView.resize(); 1171 }, 1172 1173 _enableResourceTracking: function() 1174 { 1175 if (InspectorController.resourceTrackingEnabled()) 1176 return; 1177 this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled); 1178 }, 1179 1180 _toggleResourceTracking: function(optionalAlways) 1181 { 1182 if (InspectorController.resourceTrackingEnabled()) { 1183 this.largerResourcesButton.visible = false; 1184 this.sortingSelectElement.visible = false; 1185 InspectorController.disableResourceTracking(true); 1186 } else { 1187 this.largerResourcesButton.visible = true; 1188 this.sortingSelectElement.visible = true; 1189 InspectorController.enableResourceTracking(!!optionalAlways); 1190 } 1191 } 1192} 1193 1194WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype; 1195 1196WebInspector.ResourceCalculator = function() 1197{ 1198} 1199 1200WebInspector.ResourceCalculator.prototype = { 1201 computeSummaryValues: function(resources) 1202 { 1203 var total = 0; 1204 var categoryValues = {}; 1205 1206 var resourcesLength = resources.length; 1207 for (var i = 0; i < resourcesLength; ++i) { 1208 var resource = resources[i]; 1209 var value = this._value(resource); 1210 if (typeof value === "undefined") 1211 continue; 1212 if (!(resource.category.name in categoryValues)) 1213 categoryValues[resource.category.name] = 0; 1214 categoryValues[resource.category.name] += value; 1215 total += value; 1216 } 1217 1218 return {categoryValues: categoryValues, total: total}; 1219 }, 1220 1221 computeBarGraphPercentages: function(resource) 1222 { 1223 return {start: 0, middle: 0, end: (this._value(resource) / this.boundarySpan) * 100}; 1224 }, 1225 1226 computeBarGraphLabels: function(resource) 1227 { 1228 const label = this.formatValue(this._value(resource)); 1229 var tooltip = label; 1230 if (resource.cached) 1231 tooltip = WebInspector.UIString("%s (from cache)", tooltip); 1232 return {left: label, right: label, tooltip: tooltip}; 1233 }, 1234 1235 get boundarySpan() 1236 { 1237 return this.maximumBoundary - this.minimumBoundary; 1238 }, 1239 1240 updateBoundaries: function(resource) 1241 { 1242 this.minimumBoundary = 0; 1243 1244 var value = this._value(resource); 1245 if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) { 1246 this.maximumBoundary = value; 1247 return true; 1248 } 1249 1250 return false; 1251 }, 1252 1253 reset: function() 1254 { 1255 delete this.minimumBoundary; 1256 delete this.maximumBoundary; 1257 }, 1258 1259 _value: function(resource) 1260 { 1261 return 0; 1262 }, 1263 1264 formatValue: function(value) 1265 { 1266 return value.toString(); 1267 } 1268} 1269 1270WebInspector.ResourceTimeCalculator = function(startAtZero) 1271{ 1272 WebInspector.ResourceCalculator.call(this); 1273 this.startAtZero = startAtZero; 1274} 1275 1276WebInspector.ResourceTimeCalculator.prototype = { 1277 computeSummaryValues: function(resources) 1278 { 1279 var resourcesByCategory = {}; 1280 var resourcesLength = resources.length; 1281 for (var i = 0; i < resourcesLength; ++i) { 1282 var resource = resources[i]; 1283 if (!(resource.category.name in resourcesByCategory)) 1284 resourcesByCategory[resource.category.name] = []; 1285 resourcesByCategory[resource.category.name].push(resource); 1286 } 1287 1288 var earliestStart; 1289 var latestEnd; 1290 var categoryValues = {}; 1291 for (var category in resourcesByCategory) { 1292 resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime); 1293 categoryValues[category] = 0; 1294 1295 var segment = {start: -1, end: -1}; 1296 1297 var categoryResources = resourcesByCategory[category]; 1298 var resourcesLength = categoryResources.length; 1299 for (var i = 0; i < resourcesLength; ++i) { 1300 var resource = categoryResources[i]; 1301 if (resource.startTime === -1 || resource.endTime === -1) 1302 continue; 1303 1304 if (typeof earliestStart === "undefined") 1305 earliestStart = resource.startTime; 1306 else 1307 earliestStart = Math.min(earliestStart, resource.startTime); 1308 1309 if (typeof latestEnd === "undefined") 1310 latestEnd = resource.endTime; 1311 else 1312 latestEnd = Math.max(latestEnd, resource.endTime); 1313 1314 if (resource.startTime <= segment.end) { 1315 segment.end = Math.max(segment.end, resource.endTime); 1316 continue; 1317 } 1318 1319 categoryValues[category] += segment.end - segment.start; 1320 1321 segment.start = resource.startTime; 1322 segment.end = resource.endTime; 1323 } 1324 1325 // Add the last segment 1326 categoryValues[category] += segment.end - segment.start; 1327 } 1328 1329 return {categoryValues: categoryValues, total: latestEnd - earliestStart}; 1330 }, 1331 1332 computeBarGraphPercentages: function(resource) 1333 { 1334 if (resource.startTime !== -1) 1335 var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100; 1336 else 1337 var start = 0; 1338 1339 if (resource.responseReceivedTime !== -1) 1340 var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100; 1341 else 1342 var middle = (this.startAtZero ? start : 100); 1343 1344 if (resource.endTime !== -1) 1345 var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100; 1346 else 1347 var end = (this.startAtZero ? middle : 100); 1348 1349 if (this.startAtZero) { 1350 end -= start; 1351 middle -= start; 1352 start = 0; 1353 } 1354 1355 return {start: start, middle: middle, end: end}; 1356 }, 1357 1358 computeBarGraphLabels: function(resource) 1359 { 1360 var leftLabel = ""; 1361 if (resource.latency > 0) 1362 leftLabel = this.formatValue(resource.latency); 1363 1364 var rightLabel = ""; 1365 if (resource.responseReceivedTime !== -1 && resource.endTime !== -1) 1366 rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime); 1367 1368 if (leftLabel && rightLabel) { 1369 var total = this.formatValue(resource.duration); 1370 var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total); 1371 } else if (leftLabel) 1372 var tooltip = WebInspector.UIString("%s latency", leftLabel); 1373 else if (rightLabel) 1374 var tooltip = WebInspector.UIString("%s download", rightLabel); 1375 1376 if (resource.cached) 1377 tooltip = WebInspector.UIString("%s (from cache)", tooltip); 1378 1379 return {left: leftLabel, right: rightLabel, tooltip: tooltip}; 1380 }, 1381 1382 updateBoundaries: function(resource) 1383 { 1384 var didChange = false; 1385 1386 var lowerBound; 1387 if (this.startAtZero) 1388 lowerBound = 0; 1389 else 1390 lowerBound = this._lowerBound(resource); 1391 1392 if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) { 1393 this.minimumBoundary = lowerBound; 1394 didChange = true; 1395 } 1396 1397 var upperBound = this._upperBound(resource); 1398 if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) { 1399 this.maximumBoundary = upperBound; 1400 didChange = true; 1401 } 1402 1403 return didChange; 1404 }, 1405 1406 formatValue: function(value) 1407 { 1408 return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); 1409 }, 1410 1411 _lowerBound: function(resource) 1412 { 1413 return 0; 1414 }, 1415 1416 _upperBound: function(resource) 1417 { 1418 return 0; 1419 }, 1420} 1421 1422WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype; 1423 1424WebInspector.ResourceTransferTimeCalculator = function() 1425{ 1426 WebInspector.ResourceTimeCalculator.call(this, false); 1427} 1428 1429WebInspector.ResourceTransferTimeCalculator.prototype = { 1430 formatValue: function(value) 1431 { 1432 return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); 1433 }, 1434 1435 _lowerBound: function(resource) 1436 { 1437 return resource.startTime; 1438 }, 1439 1440 _upperBound: function(resource) 1441 { 1442 return resource.endTime; 1443 } 1444} 1445 1446WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype; 1447 1448WebInspector.ResourceTransferDurationCalculator = function() 1449{ 1450 WebInspector.ResourceTimeCalculator.call(this, true); 1451} 1452 1453WebInspector.ResourceTransferDurationCalculator.prototype = { 1454 formatValue: function(value) 1455 { 1456 return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); 1457 }, 1458 1459 _upperBound: function(resource) 1460 { 1461 return resource.duration; 1462 } 1463} 1464 1465WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype; 1466 1467WebInspector.ResourceTransferSizeCalculator = function() 1468{ 1469 WebInspector.ResourceCalculator.call(this); 1470} 1471 1472WebInspector.ResourceTransferSizeCalculator.prototype = { 1473 _value: function(resource) 1474 { 1475 return resource.contentLength; 1476 }, 1477 1478 formatValue: function(value) 1479 { 1480 return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector)); 1481 } 1482} 1483 1484WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype; 1485 1486WebInspector.ResourceSidebarTreeElement = function(resource) 1487{ 1488 this.resource = resource; 1489 1490 this.createIconElement(); 1491 1492 WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource); 1493 1494 this.refreshTitles(); 1495} 1496 1497WebInspector.ResourceSidebarTreeElement.prototype = { 1498 onattach: function() 1499 { 1500 WebInspector.SidebarTreeElement.prototype.onattach.call(this); 1501 1502 var link = document.createElement("a"); 1503 link.href = this.resource.url; 1504 link.className = "invisible"; 1505 while (this._listItemNode.firstChild) 1506 link.appendChild(this._listItemNode.firstChild); 1507 this._listItemNode.appendChild(link); 1508 this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); 1509 }, 1510 1511 onselect: function() 1512 { 1513 WebInspector.panels.resources.showResource(this.resource); 1514 }, 1515 1516 ondblclick: function(treeElement, event) 1517 { 1518 InspectorController.inspectedWindow().open(this.resource.url); 1519 }, 1520 1521 get mainTitle() 1522 { 1523 return this.resource.displayName; 1524 }, 1525 1526 set mainTitle(x) 1527 { 1528 // Do nothing. 1529 }, 1530 1531 get subtitle() 1532 { 1533 var subtitle = this.resource.displayDomain; 1534 1535 if (this.resource.path && this.resource.lastPathComponent) { 1536 var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent); 1537 if (lastPathComponentIndex != -1) 1538 subtitle += this.resource.path.substring(0, lastPathComponentIndex); 1539 } 1540 1541 return subtitle; 1542 }, 1543 1544 set subtitle(x) 1545 { 1546 // Do nothing. 1547 }, 1548 1549 createIconElement: function() 1550 { 1551 var previousIconElement = this.iconElement; 1552 1553 if (this.resource.category === WebInspector.resourceCategories.images) { 1554 var previewImage = document.createElement("img"); 1555 previewImage.className = "image-resource-icon-preview"; 1556 previewImage.src = this.resource.url; 1557 1558 this.iconElement = document.createElement("div"); 1559 this.iconElement.className = "icon"; 1560 this.iconElement.appendChild(previewImage); 1561 } else { 1562 this.iconElement = document.createElement("img"); 1563 this.iconElement.className = "icon"; 1564 } 1565 1566 if (previousIconElement) 1567 previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement); 1568 }, 1569 1570 refresh: function() 1571 { 1572 this.refreshTitles(); 1573 1574 if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) { 1575 this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+"); 1576 this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); 1577 1578 this.createIconElement(); 1579 } 1580 }, 1581 1582 resetBubble: function() 1583 { 1584 this.bubbleText = ""; 1585 this.bubbleElement.removeStyleClass("search-matches"); 1586 this.bubbleElement.removeStyleClass("warning"); 1587 this.bubbleElement.removeStyleClass("error"); 1588 }, 1589 1590 set searchMatches(matches) 1591 { 1592 this.resetBubble(); 1593 1594 if (!matches) 1595 return; 1596 1597 this.bubbleText = matches; 1598 this.bubbleElement.addStyleClass("search-matches"); 1599 }, 1600 1601 updateErrorsAndWarnings: function() 1602 { 1603 this.resetBubble(); 1604 1605 if (this.resource.warnings || this.resource.errors) 1606 this.bubbleText = (this.resource.warnings + this.resource.errors); 1607 1608 if (this.resource.warnings) 1609 this.bubbleElement.addStyleClass("warning"); 1610 1611 if (this.resource.errors) 1612 this.bubbleElement.addStyleClass("error"); 1613 } 1614} 1615 1616WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b) 1617{ 1618 return WebInspector.Resource.CompareByStartTime(a.resource, b.resource) 1619 || WebInspector.Resource.CompareByEndTime(a.resource, b.resource) 1620 || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource); 1621} 1622 1623WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b) 1624{ 1625 return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource) 1626 || WebInspector.Resource.CompareByStartTime(a.resource, b.resource) 1627 || WebInspector.Resource.CompareByEndTime(a.resource, b.resource); 1628} 1629 1630WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b) 1631{ 1632 return WebInspector.Resource.CompareByEndTime(a.resource, b.resource) 1633 || WebInspector.Resource.CompareByStartTime(a.resource, b.resource) 1634 || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource); 1635} 1636 1637WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b) 1638{ 1639 return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource); 1640} 1641 1642WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b) 1643{ 1644 return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource); 1645} 1646 1647WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b) 1648{ 1649 return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource); 1650} 1651 1652WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; 1653 1654WebInspector.ResourceGraph = function(resource) 1655{ 1656 this.resource = resource; 1657 1658 this._graphElement = document.createElement("div"); 1659 this._graphElement.className = "resources-graph-side"; 1660 this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false); 1661 1662 if (resource.cached) 1663 this._graphElement.addStyleClass("resource-cached"); 1664 1665 this._barAreaElement = document.createElement("div"); 1666 this._barAreaElement.className = "resources-graph-bar-area hidden"; 1667 this._graphElement.appendChild(this._barAreaElement); 1668 1669 this._barLeftElement = document.createElement("div"); 1670 this._barLeftElement.className = "resources-graph-bar waiting"; 1671 this._barAreaElement.appendChild(this._barLeftElement); 1672 1673 this._barRightElement = document.createElement("div"); 1674 this._barRightElement.className = "resources-graph-bar"; 1675 this._barAreaElement.appendChild(this._barRightElement); 1676 1677 this._labelLeftElement = document.createElement("div"); 1678 this._labelLeftElement.className = "resources-graph-label waiting"; 1679 this._barAreaElement.appendChild(this._labelLeftElement); 1680 1681 this._labelRightElement = document.createElement("div"); 1682 this._labelRightElement.className = "resources-graph-label"; 1683 this._barAreaElement.appendChild(this._labelRightElement); 1684 1685 this._graphElement.addStyleClass("resources-category-" + resource.category.name); 1686} 1687 1688WebInspector.ResourceGraph.prototype = { 1689 get graphElement() 1690 { 1691 return this._graphElement; 1692 }, 1693 1694 refreshLabelPositions: function() 1695 { 1696 this._labelLeftElement.style.removeProperty("left"); 1697 this._labelLeftElement.style.removeProperty("right"); 1698 this._labelLeftElement.removeStyleClass("before"); 1699 this._labelLeftElement.removeStyleClass("hidden"); 1700 1701 this._labelRightElement.style.removeProperty("left"); 1702 this._labelRightElement.style.removeProperty("right"); 1703 this._labelRightElement.removeStyleClass("after"); 1704 this._labelRightElement.removeStyleClass("hidden"); 1705 1706 const labelPadding = 10; 1707 const rightBarWidth = (this._barRightElement.offsetWidth - labelPadding); 1708 const leftBarWidth = ((this._barLeftElement.offsetWidth - this._barRightElement.offsetWidth) - labelPadding); 1709 1710 var labelBefore = (this._labelLeftElement.offsetWidth > leftBarWidth); 1711 var labelAfter = (this._labelRightElement.offsetWidth > rightBarWidth); 1712 1713 if (labelBefore) { 1714 if ((this._graphElement.offsetWidth * (this._percentages.start / 100)) < (this._labelLeftElement.offsetWidth + 10)) 1715 this._labelLeftElement.addStyleClass("hidden"); 1716 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%"); 1717 this._labelLeftElement.addStyleClass("before"); 1718 } else { 1719 this._labelLeftElement.style.setProperty("left", this._percentages.start + "%"); 1720 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%"); 1721 } 1722 1723 if (labelAfter) { 1724 if ((this._graphElement.offsetWidth * ((100 - this._percentages.end) / 100)) < (this._labelRightElement.offsetWidth + 10)) 1725 this._labelRightElement.addStyleClass("hidden"); 1726 this._labelRightElement.style.setProperty("left", this._percentages.end + "%"); 1727 this._labelRightElement.addStyleClass("after"); 1728 } else { 1729 this._labelRightElement.style.setProperty("left", this._percentages.middle + "%"); 1730 this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%"); 1731 } 1732 }, 1733 1734 refresh: function(calculator) 1735 { 1736 var percentages = calculator.computeBarGraphPercentages(this.resource); 1737 var labels = calculator.computeBarGraphLabels(this.resource); 1738 1739 this._percentages = percentages; 1740 1741 this._barAreaElement.removeStyleClass("hidden"); 1742 1743 if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) { 1744 this._graphElement.removeMatchingStyleClasses("resources-category-\\w+"); 1745 this._graphElement.addStyleClass("resources-category-" + this.resource.category.name); 1746 } 1747 1748 this._barLeftElement.style.setProperty("left", percentages.start + "%"); 1749 this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); 1750 1751 this._barRightElement.style.setProperty("left", percentages.middle + "%"); 1752 this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); 1753 1754 this._labelLeftElement.textContent = labels.left; 1755 this._labelRightElement.textContent = labels.right; 1756 1757 var tooltip = (labels.tooltip || ""); 1758 this._barLeftElement.title = tooltip; 1759 this._labelLeftElement.title = tooltip; 1760 this._labelRightElement.title = tooltip; 1761 this._barRightElement.title = tooltip; 1762 } 1763} 1764