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.AbstractTimelinePanel.call(this); 33 34 this.element.addStyleClass("resources"); 35 36 this._createPanelEnabler(); 37 38 this.viewsContainerElement = document.createElement("div"); 39 this.viewsContainerElement.id = "resource-views"; 40 this.element.appendChild(this.viewsContainerElement); 41 42 this.createFilterPanel(); 43 this.createInterface(); 44 45 this._createStatusbarButtons(); 46 47 this.reset(); 48 this.filter(this.filterAllElement, false); 49 this.graphsTreeElement.children[0].select(); 50} 51 52WebInspector.ResourcesPanel.prototype = { 53 toolbarItemClass: "resources", 54 55 get toolbarItemLabel() 56 { 57 return WebInspector.UIString("Resources"); 58 }, 59 60 get statusBarItems() 61 { 62 return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement]; 63 }, 64 65 get categories() 66 { 67 return WebInspector.resourceCategories; 68 }, 69 70 createItemTreeElement: function(item) 71 { 72 return new WebInspector.ResourceSidebarTreeElement(item); 73 }, 74 75 createItemGraph: function(item) 76 { 77 return new WebInspector.ResourceGraph(item); 78 }, 79 80 isCategoryVisible: function(categoryName) 81 { 82 return (this.itemsGraphsElement.hasStyleClass("filter-all") || this.itemsGraphsElement.hasStyleClass("filter-" + categoryName.toLowerCase())); 83 }, 84 85 populateSidebar: function() 86 { 87 var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time")); 88 timeGraphItem.onselect = this._graphSelected.bind(this); 89 90 var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator(); 91 var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator(); 92 93 timeGraphItem.sortingOptions = [ 94 { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator }, 95 { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator }, 96 { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator }, 97 { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator }, 98 { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator }, 99 ]; 100 101 timeGraphItem.selectedSortingOptionIndex = 1; 102 103 var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size")); 104 sizeGraphItem.onselect = this._graphSelected.bind(this); 105 106 var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator(); 107 sizeGraphItem.sortingOptions = [ 108 { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator }, 109 ]; 110 111 sizeGraphItem.selectedSortingOptionIndex = 0; 112 113 this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true); 114 this.sidebarTree.appendChild(this.graphsTreeElement); 115 116 this.graphsTreeElement.appendChild(timeGraphItem); 117 this.graphsTreeElement.appendChild(sizeGraphItem); 118 this.graphsTreeElement.expand(); 119 120 this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true); 121 this.sidebarTree.appendChild(this.itemsTreeElement); 122 123 this.itemsTreeElement.expand(); 124 }, 125 126 _createPanelEnabler: function() 127 { 128 var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel."); 129 var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower."); 130 var panelEnablerButton = WebInspector.UIString("Enable resource tracking"); 131 132 this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); 133 this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this); 134 135 this.element.appendChild(this.panelEnablerView.element); 136 137 this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); 138 this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false); 139 }, 140 141 _createStatusbarButtons: function() 142 { 143 this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item"); 144 145 WebInspector.settings.addEventListener("loaded", this._settingsLoaded, this); 146 this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false); 147 this.sortingSelectElement = document.createElement("select"); 148 this.sortingSelectElement.className = "status-bar-item"; 149 this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false); 150 }, 151 152 _settingsLoaded: function() 153 { 154 this.largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows; 155 if (!WebInspector.settings.resourcesLargeRows) 156 this._setLargerResources(WebInspector.settings.resourcesLargeRows); 157 }, 158 159 get mainResourceLoadTime() 160 { 161 return this._mainResourceLoadTime || -1; 162 }, 163 164 set mainResourceLoadTime(x) 165 { 166 if (this._mainResourceLoadTime === x) 167 return; 168 169 this._mainResourceLoadTime = x; 170 171 // Update the dividers to draw the new line 172 this.updateGraphDividersIfNeeded(true); 173 }, 174 175 get mainResourceDOMContentTime() 176 { 177 return this._mainResourceDOMContentTime || -1; 178 }, 179 180 set mainResourceDOMContentTime(x) 181 { 182 if (this._mainResourceDOMContentTime === x) 183 return; 184 185 this._mainResourceDOMContentTime = x; 186 187 this.updateGraphDividersIfNeeded(true); 188 }, 189 190 show: function() 191 { 192 WebInspector.AbstractTimelinePanel.prototype.show.call(this); 193 194 var visibleView = this.visibleView; 195 if (this.visibleResource) { 196 this.visibleView.headersVisible = true; 197 this.visibleView.show(this.viewsContainerElement); 198 } else if (visibleView) 199 visibleView.show(); 200 201 // Hide any views that are visible that are not this panel's current visible view. 202 // This can happen when a ResourceView is visible in the Scripts panel then switched 203 // to the this panel. 204 var resourcesLength = this._resources.length; 205 for (var i = 0; i < resourcesLength; ++i) { 206 var resource = this._resources[i]; 207 var view = resource._resourcesView; 208 if (!view || view === visibleView) 209 continue; 210 view.visible = false; 211 } 212 }, 213 214 get searchableViews() 215 { 216 var views = []; 217 218 const visibleView = this.visibleView; 219 if (visibleView && visibleView.performSearch) 220 views.push(visibleView); 221 222 var resourcesLength = this._resources.length; 223 for (var i = 0; i < resourcesLength; ++i) { 224 var resource = this._resources[i]; 225 if (!resource._itemsTreeElement) 226 continue; 227 var resourceView = this.resourceViewForResource(resource); 228 if (!resourceView.performSearch || resourceView === visibleView) 229 continue; 230 views.push(resourceView); 231 } 232 233 return views; 234 }, 235 236 get searchResultsSortFunction() 237 { 238 const resourceTreeElementSortFunction = this.sortingFunction; 239 240 function sortFuction(a, b) 241 { 242 return resourceTreeElementSortFunction(a.resource._itemsTreeElement, b.resource._itemsTreeElement); 243 } 244 245 return sortFuction; 246 }, 247 248 searchMatchFound: function(view, matches) 249 { 250 view.resource._itemsTreeElement.searchMatches = matches; 251 }, 252 253 searchCanceled: function(startingNewSearch) 254 { 255 WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); 256 257 if (startingNewSearch || !this._resources) 258 return; 259 260 for (var i = 0; i < this._resources.length; ++i) { 261 var resource = this._resources[i]; 262 if (resource._itemsTreeElement) 263 resource._itemsTreeElement.updateErrorsAndWarnings(); 264 } 265 }, 266 267 performSearch: function(query) 268 { 269 for (var i = 0; i < this._resources.length; ++i) { 270 var resource = this._resources[i]; 271 if (resource._itemsTreeElement) 272 resource._itemsTreeElement.resetBubble(); 273 } 274 275 WebInspector.Panel.prototype.performSearch.call(this, query); 276 }, 277 278 get visibleView() 279 { 280 if (this.visibleResource) 281 return this.visibleResource._resourcesView; 282 return InspectorBackend.resourceTrackingEnabled() ? null : this.panelEnablerView; 283 }, 284 285 get sortingFunction() 286 { 287 return this._sortingFunction; 288 }, 289 290 set sortingFunction(x) 291 { 292 this._sortingFunction = x; 293 this._sortResourcesIfNeeded(); 294 }, 295 296 refresh: function() 297 { 298 WebInspector.AbstractTimelinePanel.prototype.refresh.call(this); 299 300 this._sortResourcesIfNeeded(); 301 this._updateSummaryGraph(); 302 }, 303 304 _updateSummaryGraph: function() 305 { 306 this.summaryBar.update(this._resources); 307 }, 308 309 resourceTrackingWasEnabled: function() 310 { 311 this.reset(); 312 }, 313 314 resourceTrackingWasDisabled: function() 315 { 316 this.reset(); 317 }, 318 319 reset: function() 320 { 321 this.closeVisibleResource(); 322 323 delete this.currentQuery; 324 this.searchCanceled(); 325 326 if (this._resources) { 327 var resourcesLength = this._resources.length; 328 for (var i = 0; i < resourcesLength; ++i) { 329 var resource = this._resources[i]; 330 331 resource.warnings = 0; 332 resource.errors = 0; 333 334 delete resource._resourcesView; 335 } 336 } 337 338 WebInspector.AbstractTimelinePanel.prototype.reset.call(this); 339 340 this.mainResourceLoadTime = -1; 341 this.mainResourceDOMContentTime = -1; 342 343 this.viewsContainerElement.removeChildren(); 344 345 this.summaryBar.reset(); 346 347 if (InspectorBackend.resourceTrackingEnabled()) { 348 this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable."); 349 this.enableToggleButton.toggled = true; 350 this.largerResourcesButton.visible = true; 351 this.sortingSelectElement.removeStyleClass("hidden"); 352 this.panelEnablerView.visible = false; 353 } else { 354 this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable."); 355 this.enableToggleButton.toggled = false; 356 this.largerResourcesButton.visible = false; 357 this.sortingSelectElement.addStyleClass("hidden"); 358 this.panelEnablerView.visible = true; 359 } 360 }, 361 362 addResource: function(resource) 363 { 364 this._resources.push(resource); 365 this.refreshResource(resource); 366 }, 367 368 removeResource: function(resource) 369 { 370 if (this.visibleView === resource._resourcesView) 371 this.closeVisibleResource(); 372 373 this.removeItem(resource); 374 375 resource.warnings = 0; 376 resource.errors = 0; 377 378 delete resource._resourcesView; 379 }, 380 381 addMessageToResource: function(resource, msg) 382 { 383 if (!resource) 384 return; 385 386 switch (msg.level) { 387 case WebInspector.ConsoleMessage.MessageLevel.Warning: 388 resource.warnings += msg.repeatDelta; 389 break; 390 case WebInspector.ConsoleMessage.MessageLevel.Error: 391 resource.errors += msg.repeatDelta; 392 break; 393 } 394 395 if (!this.currentQuery && resource._itemsTreeElement) 396 resource._itemsTreeElement.updateErrorsAndWarnings(); 397 398 var view = this.resourceViewForResource(resource); 399 if (view.addMessage) 400 view.addMessage(msg); 401 }, 402 403 clearMessages: function() 404 { 405 var resourcesLength = this._resources.length; 406 for (var i = 0; i < resourcesLength; ++i) { 407 var resource = this._resources[i]; 408 resource.warnings = 0; 409 resource.errors = 0; 410 411 if (!this.currentQuery && resource._itemsTreeElement) 412 resource._itemsTreeElement.updateErrorsAndWarnings(); 413 414 var view = resource._resourcesView; 415 if (!view || !view.clearMessages) 416 continue; 417 view.clearMessages(); 418 } 419 }, 420 421 refreshResource: function(resource) 422 { 423 this.refreshItem(resource); 424 }, 425 426 recreateViewForResourceIfNeeded: function(resource) 427 { 428 if (!resource || !resource._resourcesView) 429 return; 430 431 var newView = this._createResourceView(resource); 432 if (newView.__proto__ === resource._resourcesView.__proto__) 433 return; 434 435 resource.warnings = 0; 436 resource.errors = 0; 437 438 if (!this.currentQuery && resource._itemsTreeElement) 439 resource._itemsTreeElement.updateErrorsAndWarnings(); 440 441 var oldView = resource._resourcesView; 442 var oldViewParentNode = oldView.visible ? oldView.element.parentNode : null; 443 444 resource._resourcesView.detach(); 445 delete resource._resourcesView; 446 447 resource._resourcesView = newView; 448 449 newView.headersVisible = oldView.headersVisible; 450 451 if (oldViewParentNode) 452 newView.show(oldViewParentNode); 453 }, 454 455 canShowSourceLineForURL: function(url) 456 { 457 return !!WebInspector.resourceForURL(url); 458 }, 459 460 showSourceLineForURL: function(url, line) 461 { 462 this.showResource(WebInspector.resourceForURL(url), line); 463 }, 464 465 showResource: function(resource, line) 466 { 467 if (!resource) 468 return; 469 470 this.containerElement.addStyleClass("viewing-resource"); 471 472 if (this.visibleResource && this.visibleResource._resourcesView) 473 this.visibleResource._resourcesView.hide(); 474 475 var view = this.resourceViewForResource(resource); 476 view.headersVisible = true; 477 view.show(this.viewsContainerElement); 478 479 if (line) { 480 if (view.revealLine) 481 view.revealLine(line); 482 if (view.highlightLine) 483 view.highlightLine(line); 484 } 485 486 this.revealAndSelectItem(resource); 487 488 this.visibleResource = resource; 489 490 this.updateSidebarWidth(); 491 }, 492 493 showView: function(view) 494 { 495 if (!view) 496 return; 497 this.showResource(view.resource); 498 }, 499 500 closeVisibleResource: function() 501 { 502 this.containerElement.removeStyleClass("viewing-resource"); 503 this._updateDividersLabelBarPosition(); 504 505 if (this.visibleResource && this.visibleResource._resourcesView) 506 this.visibleResource._resourcesView.hide(); 507 delete this.visibleResource; 508 509 if (this._lastSelectedGraphTreeElement) 510 this._lastSelectedGraphTreeElement.select(true); 511 512 this.updateSidebarWidth(); 513 }, 514 515 resourceViewForResource: function(resource) 516 { 517 if (!resource) 518 return null; 519 if (!resource._resourcesView) 520 resource._resourcesView = this._createResourceView(resource); 521 return resource._resourcesView; 522 }, 523 524 sourceFrameForResource: function(resource) 525 { 526 var view = this.resourceViewForResource(resource); 527 if (!view) 528 return null; 529 530 if (!view.setupSourceFrameIfNeeded) 531 return null; 532 533 // Setting up the source frame requires that we be attached. 534 if (!this.element.parentNode) 535 this.attach(); 536 537 view.setupSourceFrameIfNeeded(); 538 return view.sourceFrame; 539 }, 540 541 _sortResourcesIfNeeded: function() 542 { 543 this.sortItems(this.sortingFunction); 544 }, 545 546 updateGraphDividersIfNeeded: function(force) 547 { 548 var proceed = WebInspector.AbstractTimelinePanel.prototype.updateGraphDividersIfNeeded.call(this, force); 549 550 if (!proceed) 551 return; 552 553 if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) { 554 // If our current sorting method starts at zero, that means it shows all 555 // resources starting at the same point, and so onLoad event and DOMContent 556 // event lines really wouldn't make much sense here, so don't render them. 557 // Additionally, if the calculator doesn't have the computePercentageFromEventTime 558 // function defined, we are probably sorting by size, and event times aren't relevant 559 // in this case. 560 return; 561 } 562 563 if (this.mainResourceLoadTime !== -1) { 564 var percent = this.calculator.computePercentageFromEventTime(this.mainResourceLoadTime); 565 566 var loadDivider = document.createElement("div"); 567 loadDivider.className = "resources-onload-divider"; 568 569 var loadDividerPadding = document.createElement("div"); 570 loadDividerPadding.className = "resources-event-divider-padding"; 571 loadDividerPadding.style.left = percent + "%"; 572 loadDividerPadding.title = WebInspector.UIString("Load event fired"); 573 loadDividerPadding.appendChild(loadDivider); 574 575 this.addEventDivider(loadDividerPadding); 576 } 577 578 if (this.mainResourceDOMContentTime !== -1) { 579 var percent = this.calculator.computePercentageFromEventTime(this.mainResourceDOMContentTime); 580 581 var domContentDivider = document.createElement("div"); 582 domContentDivider.className = "resources-ondomcontent-divider"; 583 584 var domContentDividerPadding = document.createElement("div"); 585 domContentDividerPadding.className = "resources-event-divider-padding"; 586 domContentDividerPadding.style.left = percent + "%"; 587 domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired"); 588 domContentDividerPadding.appendChild(domContentDivider); 589 590 this.addEventDivider(domContentDividerPadding); 591 } 592 }, 593 594 _graphSelected: function(treeElement) 595 { 596 if (this._lastSelectedGraphTreeElement) 597 this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex; 598 599 this._lastSelectedGraphTreeElement = treeElement; 600 601 this.sortingSelectElement.removeChildren(); 602 for (var i = 0; i < treeElement.sortingOptions.length; ++i) { 603 var sortingOption = treeElement.sortingOptions[i]; 604 var option = document.createElement("option"); 605 option.label = sortingOption.name; 606 option.sortingFunction = sortingOption.sortingFunction; 607 option.calculator = sortingOption.calculator; 608 this.sortingSelectElement.appendChild(option); 609 } 610 611 this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex; 612 this._changeSortingFunction(); 613 614 this.closeVisibleResource(); 615 this.containerElement.scrollTop = 0; 616 }, 617 618 _toggleLargerResources: function() 619 { 620 if (!this.itemsTreeElement._childrenListNode) 621 return; 622 623 WebInspector.settings.resourcesLargeRows = !WebInspector.settings.resourcesLargeRows; 624 this._setLargerResources(this.itemsTreeElement.smallChildren); 625 }, 626 627 _setLargerResources: function(enabled) 628 { 629 this.largerResourcesButton.toggled = enabled; 630 this.itemsTreeElement.smallChildren = !enabled; 631 if (!enabled) { 632 this.itemsGraphsElement.addStyleClass("small"); 633 this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows."); 634 this.adjustScrollPosition(); 635 } else { 636 this.itemsGraphsElement.removeStyleClass("small"); 637 this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); 638 } 639 }, 640 641 _changeSortingFunction: function() 642 { 643 var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex]; 644 this.sortingFunction = selectedOption.sortingFunction; 645 this.calculator = this.summaryBar.calculator = selectedOption.calculator; 646 }, 647 648 _createResourceView: function(resource) 649 { 650 switch (resource.category) { 651 case WebInspector.resourceCategories.documents: 652 case WebInspector.resourceCategories.stylesheets: 653 case WebInspector.resourceCategories.scripts: 654 case WebInspector.resourceCategories.xhr: 655 return new WebInspector.SourceView(resource); 656 case WebInspector.resourceCategories.images: 657 return new WebInspector.ImageView(resource); 658 case WebInspector.resourceCategories.fonts: 659 return new WebInspector.FontView(resource); 660 default: 661 return new WebInspector.ResourceView(resource); 662 } 663 }, 664 665 setSidebarWidth: function(width) 666 { 667 if (this.visibleResource) { 668 this.containerElement.style.width = width + "px"; 669 this.sidebarElement.style.removeProperty("width"); 670 } else { 671 this.sidebarElement.style.width = width + "px"; 672 this.containerElement.style.removeProperty("width"); 673 } 674 675 this.sidebarResizeElement.style.left = (width - 3) + "px"; 676 }, 677 678 updateMainViewWidth: function(width) 679 { 680 this.viewsContainerElement.style.left = width + "px"; 681 682 WebInspector.AbstractTimelinePanel.prototype.updateMainViewWidth.call(this, width); 683 this.resize(); 684 }, 685 686 _enableResourceTracking: function() 687 { 688 if (InspectorBackend.resourceTrackingEnabled()) 689 return; 690 this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled); 691 }, 692 693 _toggleResourceTracking: function(optionalAlways) 694 { 695 if (InspectorBackend.resourceTrackingEnabled()) { 696 this.largerResourcesButton.visible = false; 697 this.sortingSelectElement.visible = false; 698 InspectorBackend.disableResourceTracking(true); 699 } else { 700 this.largerResourcesButton.visible = true; 701 this.sortingSelectElement.visible = true; 702 InspectorBackend.enableResourceTracking(!!optionalAlways); 703 } 704 }, 705 706 get _resources() 707 { 708 return this.items; 709 } 710} 711 712WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype; 713 714WebInspector.getResourceContent = function(identifier, callback) 715{ 716 InspectorBackend.getResourceContent(WebInspector.Callback.wrap(callback), identifier); 717} 718 719WebInspector.didGetResourceContent = WebInspector.Callback.processCallback; 720 721WebInspector.ResourceTimeCalculator = function(startAtZero) 722{ 723 WebInspector.AbstractTimelineCalculator.call(this); 724 this.startAtZero = startAtZero; 725} 726 727WebInspector.ResourceTimeCalculator.prototype = { 728 computeSummaryValues: function(resources) 729 { 730 var resourcesByCategory = {}; 731 var resourcesLength = resources.length; 732 for (var i = 0; i < resourcesLength; ++i) { 733 var resource = resources[i]; 734 if (!(resource.category.name in resourcesByCategory)) 735 resourcesByCategory[resource.category.name] = []; 736 resourcesByCategory[resource.category.name].push(resource); 737 } 738 739 var earliestStart; 740 var latestEnd; 741 var categoryValues = {}; 742 for (var category in resourcesByCategory) { 743 resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime); 744 categoryValues[category] = 0; 745 746 var segment = {start: -1, end: -1}; 747 748 var categoryResources = resourcesByCategory[category]; 749 var resourcesLength = categoryResources.length; 750 for (var i = 0; i < resourcesLength; ++i) { 751 var resource = categoryResources[i]; 752 if (resource.startTime === -1 || resource.endTime === -1) 753 continue; 754 755 if (typeof earliestStart === "undefined") 756 earliestStart = resource.startTime; 757 else 758 earliestStart = Math.min(earliestStart, resource.startTime); 759 760 if (typeof latestEnd === "undefined") 761 latestEnd = resource.endTime; 762 else 763 latestEnd = Math.max(latestEnd, resource.endTime); 764 765 if (resource.startTime <= segment.end) { 766 segment.end = Math.max(segment.end, resource.endTime); 767 continue; 768 } 769 770 categoryValues[category] += segment.end - segment.start; 771 772 segment.start = resource.startTime; 773 segment.end = resource.endTime; 774 } 775 776 // Add the last segment 777 categoryValues[category] += segment.end - segment.start; 778 } 779 780 return {categoryValues: categoryValues, total: latestEnd - earliestStart}; 781 }, 782 783 computeBarGraphPercentages: function(resource) 784 { 785 if (resource.startTime !== -1) 786 var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100; 787 else 788 var start = 0; 789 790 if (resource.responseReceivedTime !== -1) 791 var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100; 792 else 793 var middle = (this.startAtZero ? start : 100); 794 795 if (resource.endTime !== -1) 796 var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100; 797 else 798 var end = (this.startAtZero ? middle : 100); 799 800 if (this.startAtZero) { 801 end -= start; 802 middle -= start; 803 start = 0; 804 } 805 806 return {start: start, middle: middle, end: end}; 807 }, 808 809 computePercentageFromEventTime: function(eventTime) 810 { 811 // This function computes a percentage in terms of the total loading time 812 // of a specific event. If startAtZero is set, then this is useless, and we 813 // want to return 0. 814 if (eventTime !== -1 && !this.startAtZero) 815 return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100; 816 817 return 0; 818 }, 819 820 computeBarGraphLabels: function(resource) 821 { 822 var leftLabel = ""; 823 if (resource.latency > 0) 824 leftLabel = this.formatValue(resource.latency); 825 826 var rightLabel = ""; 827 if (resource.responseReceivedTime !== -1 && resource.endTime !== -1) 828 rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime); 829 830 if (leftLabel && rightLabel) { 831 var total = this.formatValue(resource.duration); 832 var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total); 833 } else if (leftLabel) 834 var tooltip = WebInspector.UIString("%s latency", leftLabel); 835 else if (rightLabel) 836 var tooltip = WebInspector.UIString("%s download", rightLabel); 837 838 if (resource.cached) 839 tooltip = WebInspector.UIString("%s (from cache)", tooltip); 840 841 return {left: leftLabel, right: rightLabel, tooltip: tooltip}; 842 }, 843 844 updateBoundaries: function(resource) 845 { 846 var didChange = false; 847 848 var lowerBound; 849 if (this.startAtZero) 850 lowerBound = 0; 851 else 852 lowerBound = this._lowerBound(resource); 853 854 if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) { 855 this.minimumBoundary = lowerBound; 856 didChange = true; 857 } 858 859 var upperBound = this._upperBound(resource); 860 if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) { 861 this.maximumBoundary = upperBound; 862 didChange = true; 863 } 864 865 return didChange; 866 }, 867 868 formatValue: function(value) 869 { 870 return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); 871 }, 872 873 _lowerBound: function(resource) 874 { 875 return 0; 876 }, 877 878 _upperBound: function(resource) 879 { 880 return 0; 881 } 882} 883 884WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype; 885 886WebInspector.ResourceTransferTimeCalculator = function() 887{ 888 WebInspector.ResourceTimeCalculator.call(this, false); 889} 890 891WebInspector.ResourceTransferTimeCalculator.prototype = { 892 formatValue: function(value) 893 { 894 return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); 895 }, 896 897 _lowerBound: function(resource) 898 { 899 return resource.startTime; 900 }, 901 902 _upperBound: function(resource) 903 { 904 return resource.endTime; 905 } 906} 907 908WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype; 909 910WebInspector.ResourceTransferDurationCalculator = function() 911{ 912 WebInspector.ResourceTimeCalculator.call(this, true); 913} 914 915WebInspector.ResourceTransferDurationCalculator.prototype = { 916 formatValue: function(value) 917 { 918 return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); 919 }, 920 921 _upperBound: function(resource) 922 { 923 return resource.duration; 924 } 925} 926 927WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype; 928 929WebInspector.ResourceTransferSizeCalculator = function() 930{ 931 WebInspector.AbstractTimelineCalculator.call(this); 932} 933 934WebInspector.ResourceTransferSizeCalculator.prototype = { 935 computeBarGraphLabels: function(resource) 936 { 937 const label = this.formatValue(this._value(resource)); 938 var tooltip = label; 939 if (resource.cached) 940 tooltip = WebInspector.UIString("%s (from cache)", tooltip); 941 return {left: label, right: label, tooltip: tooltip}; 942 }, 943 944 _value: function(resource) 945 { 946 return resource.contentLength; 947 }, 948 949 formatValue: function(value) 950 { 951 return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector)); 952 } 953} 954 955WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype; 956 957WebInspector.ResourceSidebarTreeElement = function(resource) 958{ 959 this.resource = resource; 960 961 this.createIconElement(); 962 963 WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource); 964 965 this.refreshTitles(); 966} 967 968WebInspector.ResourceSidebarTreeElement.prototype = { 969 onattach: function() 970 { 971 WebInspector.SidebarTreeElement.prototype.onattach.call(this); 972 973 this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); 974 this._listItemNode.draggable = true; 975 976 // FIXME: should actually add handler to parent, to be resolved via 977 // https://bugs.webkit.org/show_bug.cgi?id=30227 978 this._listItemNode.addEventListener("dragstart", this.ondragstart.bind(this), false); 979 this.updateErrorsAndWarnings(); 980 }, 981 982 onselect: function() 983 { 984 WebInspector.panels.resources.showResource(this.resource); 985 }, 986 987 ondblclick: function(event) 988 { 989 InjectedScriptAccess.getDefault().openInInspectedWindow(this.resource.url, function() {}); 990 }, 991 992 ondragstart: function(event) { 993 event.dataTransfer.setData("text/plain", this.resource.url); 994 event.dataTransfer.setData("text/uri-list", this.resource.url + "\r\n"); 995 event.dataTransfer.effectAllowed = "copy"; 996 return true; 997 }, 998 999 get mainTitle() 1000 { 1001 return this.resource.displayName; 1002 }, 1003 1004 set mainTitle(x) 1005 { 1006 // Do nothing. 1007 }, 1008 1009 get subtitle() 1010 { 1011 var subtitle = this.resource.displayDomain; 1012 1013 if (this.resource.path && this.resource.lastPathComponent) { 1014 var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent); 1015 if (lastPathComponentIndex != -1) 1016 subtitle += this.resource.path.substring(0, lastPathComponentIndex); 1017 } 1018 1019 return subtitle; 1020 }, 1021 1022 set subtitle(x) 1023 { 1024 // Do nothing. 1025 }, 1026 1027 get selectable() 1028 { 1029 return WebInspector.panels.resources.isCategoryVisible(this.resource.category.name); 1030 }, 1031 1032 createIconElement: function() 1033 { 1034 var previousIconElement = this.iconElement; 1035 1036 if (this.resource.category === WebInspector.resourceCategories.images) { 1037 var previewImage = document.createElement("img"); 1038 previewImage.className = "image-resource-icon-preview"; 1039 previewImage.src = this.resource.url; 1040 1041 this.iconElement = document.createElement("div"); 1042 this.iconElement.className = "icon"; 1043 this.iconElement.appendChild(previewImage); 1044 } else { 1045 this.iconElement = document.createElement("img"); 1046 this.iconElement.className = "icon"; 1047 } 1048 1049 if (previousIconElement) 1050 previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement); 1051 }, 1052 1053 refresh: function() 1054 { 1055 this.refreshTitles(); 1056 1057 if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) { 1058 this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+"); 1059 this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); 1060 1061 this.createIconElement(); 1062 } 1063 1064 this.tooltip = this.resource.url; 1065 }, 1066 1067 resetBubble: function() 1068 { 1069 this.bubbleText = ""; 1070 this.bubbleElement.removeStyleClass("search-matches"); 1071 this.bubbleElement.removeStyleClass("warning"); 1072 this.bubbleElement.removeStyleClass("error"); 1073 }, 1074 1075 set searchMatches(matches) 1076 { 1077 this.resetBubble(); 1078 1079 if (!matches) 1080 return; 1081 1082 this.bubbleText = matches; 1083 this.bubbleElement.addStyleClass("search-matches"); 1084 }, 1085 1086 updateErrorsAndWarnings: function() 1087 { 1088 this.resetBubble(); 1089 1090 if (this.resource.warnings || this.resource.errors) 1091 this.bubbleText = (this.resource.warnings + this.resource.errors); 1092 1093 if (this.resource.warnings) 1094 this.bubbleElement.addStyleClass("warning"); 1095 1096 if (this.resource.errors) 1097 this.bubbleElement.addStyleClass("error"); 1098 } 1099} 1100 1101WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b) 1102{ 1103 return WebInspector.Resource.CompareByStartTime(a.resource, b.resource) 1104 || WebInspector.Resource.CompareByEndTime(a.resource, b.resource) 1105 || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource); 1106} 1107 1108WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b) 1109{ 1110 return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource) 1111 || WebInspector.Resource.CompareByStartTime(a.resource, b.resource) 1112 || WebInspector.Resource.CompareByEndTime(a.resource, b.resource); 1113} 1114 1115WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b) 1116{ 1117 return WebInspector.Resource.CompareByEndTime(a.resource, b.resource) 1118 || WebInspector.Resource.CompareByStartTime(a.resource, b.resource) 1119 || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource); 1120} 1121 1122WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b) 1123{ 1124 return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource); 1125} 1126 1127WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b) 1128{ 1129 return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource); 1130} 1131 1132WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b) 1133{ 1134 return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource); 1135} 1136 1137WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; 1138 1139WebInspector.ResourceGraph = function(resource) 1140{ 1141 this.resource = resource; 1142 1143 this._graphElement = document.createElement("div"); 1144 this._graphElement.className = "resources-graph-side"; 1145 this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false); 1146 1147 if (resource.cached) 1148 this._graphElement.addStyleClass("resource-cached"); 1149 1150 this._barAreaElement = document.createElement("div"); 1151 this._barAreaElement.className = "resources-graph-bar-area hidden"; 1152 this._graphElement.appendChild(this._barAreaElement); 1153 1154 this._barLeftElement = document.createElement("div"); 1155 this._barLeftElement.className = "resources-graph-bar waiting"; 1156 this._barAreaElement.appendChild(this._barLeftElement); 1157 1158 this._barRightElement = document.createElement("div"); 1159 this._barRightElement.className = "resources-graph-bar"; 1160 this._barAreaElement.appendChild(this._barRightElement); 1161 1162 this._labelLeftElement = document.createElement("div"); 1163 this._labelLeftElement.className = "resources-graph-label waiting"; 1164 this._barAreaElement.appendChild(this._labelLeftElement); 1165 1166 this._labelRightElement = document.createElement("div"); 1167 this._labelRightElement.className = "resources-graph-label"; 1168 this._barAreaElement.appendChild(this._labelRightElement); 1169 1170 this._graphElement.addStyleClass("resources-category-" + resource.category.name); 1171} 1172 1173WebInspector.ResourceGraph.prototype = { 1174 get graphElement() 1175 { 1176 return this._graphElement; 1177 }, 1178 1179 refreshLabelPositions: function() 1180 { 1181 this._labelLeftElement.style.removeProperty("left"); 1182 this._labelLeftElement.style.removeProperty("right"); 1183 this._labelLeftElement.removeStyleClass("before"); 1184 this._labelLeftElement.removeStyleClass("hidden"); 1185 1186 this._labelRightElement.style.removeProperty("left"); 1187 this._labelRightElement.style.removeProperty("right"); 1188 this._labelRightElement.removeStyleClass("after"); 1189 this._labelRightElement.removeStyleClass("hidden"); 1190 1191 const labelPadding = 10; 1192 const barRightElementOffsetWidth = this._barRightElement.offsetWidth; 1193 const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth; 1194 const rightBarWidth = (barRightElementOffsetWidth - labelPadding); 1195 const leftBarWidth = ((barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding); 1196 const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth; 1197 const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth; 1198 1199 const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth); 1200 const labelAfter = (labelRightElementOffsetWidth > rightBarWidth); 1201 const graphElementOffsetWidth = this._graphElement.offsetWidth; 1202 1203 if (labelBefore) { 1204 if ((graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10)) 1205 this._labelLeftElement.addStyleClass("hidden"); 1206 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%"); 1207 this._labelLeftElement.addStyleClass("before"); 1208 } else { 1209 this._labelLeftElement.style.setProperty("left", this._percentages.start + "%"); 1210 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%"); 1211 } 1212 1213 if (labelAfter) { 1214 if ((graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10)) 1215 this._labelRightElement.addStyleClass("hidden"); 1216 this._labelRightElement.style.setProperty("left", this._percentages.end + "%"); 1217 this._labelRightElement.addStyleClass("after"); 1218 } else { 1219 this._labelRightElement.style.setProperty("left", this._percentages.middle + "%"); 1220 this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%"); 1221 } 1222 }, 1223 1224 refresh: function(calculator) 1225 { 1226 var percentages = calculator.computeBarGraphPercentages(this.resource); 1227 var labels = calculator.computeBarGraphLabels(this.resource); 1228 1229 this._percentages = percentages; 1230 1231 this._barAreaElement.removeStyleClass("hidden"); 1232 1233 if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) { 1234 this._graphElement.removeMatchingStyleClasses("resources-category-\\w+"); 1235 this._graphElement.addStyleClass("resources-category-" + this.resource.category.name); 1236 } 1237 1238 this._barLeftElement.style.setProperty("left", percentages.start + "%"); 1239 this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); 1240 1241 this._barRightElement.style.setProperty("left", percentages.middle + "%"); 1242 this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); 1243 1244 this._labelLeftElement.textContent = labels.left; 1245 this._labelRightElement.textContent = labels.right; 1246 1247 var tooltip = (labels.tooltip || ""); 1248 this._barLeftElement.title = tooltip; 1249 this._labelLeftElement.title = tooltip; 1250 this._labelRightElement.title = tooltip; 1251 this._barRightElement.title = tooltip; 1252 } 1253} 1254