| /* |
| * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WebInspector.ResourcesPanel = function() |
| { |
| WebInspector.AbstractTimelinePanel.call(this); |
| |
| this.element.addStyleClass("resources"); |
| |
| this._createPanelEnabler(); |
| |
| this.viewsContainerElement = document.createElement("div"); |
| this.viewsContainerElement.id = "resource-views"; |
| this.element.appendChild(this.viewsContainerElement); |
| |
| this.createFilterPanel(); |
| this.createInterface(); |
| |
| this._createStatusbarButtons(); |
| |
| this.reset(); |
| this.filter(this.filterAllElement, false); |
| this.graphsTreeElement.children[0].select(); |
| this._resourceTrackingEnabled = false; |
| } |
| |
| WebInspector.ResourcesPanel.prototype = { |
| toolbarItemClass: "resources", |
| |
| get toolbarItemLabel() |
| { |
| return WebInspector.UIString("Resources"); |
| }, |
| |
| get statusBarItems() |
| { |
| return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement]; |
| }, |
| |
| get categories() |
| { |
| return WebInspector.resourceCategories; |
| }, |
| |
| createItemTreeElement: function(item) |
| { |
| return new WebInspector.ResourceSidebarTreeElement(item); |
| }, |
| |
| createItemGraph: function(item) |
| { |
| return new WebInspector.ResourceGraph(item); |
| }, |
| |
| isCategoryVisible: function(categoryName) |
| { |
| return (this.itemsGraphsElement.hasStyleClass("filter-all") || this.itemsGraphsElement.hasStyleClass("filter-" + categoryName.toLowerCase())); |
| }, |
| |
| populateSidebar: function() |
| { |
| var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time")); |
| timeGraphItem.onselect = this._graphSelected.bind(this); |
| |
| var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator(); |
| var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator(); |
| |
| timeGraphItem.sortingOptions = [ |
| { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator }, |
| { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator }, |
| { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator }, |
| { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator }, |
| { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator }, |
| ]; |
| |
| timeGraphItem.isBarOpaqueAtLeft = false; |
| timeGraphItem.selectedSortingOptionIndex = 1; |
| |
| var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size")); |
| sizeGraphItem.onselect = this._graphSelected.bind(this); |
| |
| var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator(); |
| sizeGraphItem.sortingOptions = [ |
| { name: WebInspector.UIString("Sort by Transfer Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize, calculator: transferSizeCalculator }, |
| { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator }, |
| ]; |
| |
| sizeGraphItem.isBarOpaqueAtLeft = true; |
| sizeGraphItem.selectedSortingOptionIndex = 0; |
| |
| this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true); |
| this.sidebarTree.appendChild(this.graphsTreeElement); |
| |
| this.graphsTreeElement.appendChild(timeGraphItem); |
| this.graphsTreeElement.appendChild(sizeGraphItem); |
| this.graphsTreeElement.expand(); |
| |
| this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true); |
| this.sidebarTree.appendChild(this.itemsTreeElement); |
| |
| this.itemsTreeElement.expand(); |
| }, |
| |
| get resourceTrackingEnabled() |
| { |
| return this._resourceTrackingEnabled; |
| }, |
| |
| _createPanelEnabler: function() |
| { |
| var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel."); |
| var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower."); |
| var panelEnablerButton = WebInspector.UIString("Enable resource tracking"); |
| |
| this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); |
| this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this); |
| |
| this.element.appendChild(this.panelEnablerView.element); |
| |
| this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); |
| this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false); |
| }, |
| |
| _createStatusbarButtons: function() |
| { |
| this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item"); |
| |
| WebInspector.settings.addEventListener("loaded", this._settingsLoaded, this); |
| this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false); |
| this.sortingSelectElement = document.createElement("select"); |
| this.sortingSelectElement.className = "status-bar-item"; |
| this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false); |
| }, |
| |
| _settingsLoaded: function() |
| { |
| this.largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows; |
| if (!WebInspector.settings.resourcesLargeRows) |
| this._setLargerResources(WebInspector.settings.resourcesLargeRows); |
| }, |
| |
| get mainResourceLoadTime() |
| { |
| return this._mainResourceLoadTime || -1; |
| }, |
| |
| set mainResourceLoadTime(x) |
| { |
| if (this._mainResourceLoadTime === x) |
| return; |
| |
| this._mainResourceLoadTime = x; |
| |
| // Update the dividers to draw the new line |
| this.updateGraphDividersIfNeeded(true); |
| }, |
| |
| get mainResourceDOMContentTime() |
| { |
| return this._mainResourceDOMContentTime || -1; |
| }, |
| |
| set mainResourceDOMContentTime(x) |
| { |
| if (this._mainResourceDOMContentTime === x) |
| return; |
| |
| this._mainResourceDOMContentTime = x; |
| |
| this.updateGraphDividersIfNeeded(true); |
| }, |
| |
| show: function() |
| { |
| WebInspector.AbstractTimelinePanel.prototype.show.call(this); |
| |
| var visibleView = this.visibleView; |
| if (this.visibleResource) { |
| this.visibleView.headersVisible = true; |
| this.visibleView.show(this.viewsContainerElement); |
| } else if (visibleView) |
| visibleView.show(); |
| |
| // Hide any views that are visible that are not this panel's current visible view. |
| // This can happen when a ResourceView is visible in the Scripts panel then switched |
| // to the this panel. |
| var resourcesLength = this._resources.length; |
| for (var i = 0; i < resourcesLength; ++i) { |
| var resource = this._resources[i]; |
| var view = resource._resourcesView; |
| if (!view || view === visibleView) |
| continue; |
| view.visible = false; |
| } |
| }, |
| |
| get searchableViews() |
| { |
| var views = []; |
| |
| const visibleView = this.visibleView; |
| if (visibleView && visibleView.performSearch) |
| views.push(visibleView); |
| |
| var resourcesLength = this._resources.length; |
| for (var i = 0; i < resourcesLength; ++i) { |
| var resource = this._resources[i]; |
| if (!resource._itemsTreeElement) |
| continue; |
| var resourceView = this.resourceViewForResource(resource); |
| if (!resourceView.performSearch || resourceView === visibleView) |
| continue; |
| views.push(resourceView); |
| } |
| |
| return views; |
| }, |
| |
| get searchResultsSortFunction() |
| { |
| const resourceTreeElementSortFunction = this.sortingFunction; |
| |
| function sortFuction(a, b) |
| { |
| return resourceTreeElementSortFunction(a.resource._itemsTreeElement, b.resource._itemsTreeElement); |
| } |
| |
| return sortFuction; |
| }, |
| |
| searchMatchFound: function(view, matches) |
| { |
| view.resource._itemsTreeElement.searchMatches = matches; |
| }, |
| |
| searchCanceled: function(startingNewSearch) |
| { |
| WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); |
| |
| if (startingNewSearch || !this._resources) |
| return; |
| |
| for (var i = 0; i < this._resources.length; ++i) { |
| var resource = this._resources[i]; |
| if (resource._itemsTreeElement) |
| resource._itemsTreeElement.updateErrorsAndWarnings(); |
| } |
| }, |
| |
| performSearch: function(query) |
| { |
| for (var i = 0; i < this._resources.length; ++i) { |
| var resource = this._resources[i]; |
| if (resource._itemsTreeElement) |
| resource._itemsTreeElement.resetBubble(); |
| } |
| |
| WebInspector.Panel.prototype.performSearch.call(this, query); |
| }, |
| |
| get visibleView() |
| { |
| if (this.visibleResource) |
| return this.visibleResource._resourcesView; |
| return this._resourceTrackingEnabled ? null : this.panelEnablerView; |
| }, |
| |
| get sortingFunction() |
| { |
| return this._sortingFunction; |
| }, |
| |
| set sortingFunction(x) |
| { |
| this._sortingFunction = x; |
| this._sortResourcesIfNeeded(); |
| }, |
| |
| refresh: function() |
| { |
| WebInspector.AbstractTimelinePanel.prototype.refresh.call(this); |
| |
| this._sortResourcesIfNeeded(); |
| this._updateSummaryGraph(); |
| }, |
| |
| _updateSummaryGraph: function() |
| { |
| this.summaryBar.update(this._resources); |
| }, |
| |
| resourceTrackingWasEnabled: function() |
| { |
| this._resourceTrackingEnabled = true; |
| this.reset(); |
| }, |
| |
| resourceTrackingWasDisabled: function() |
| { |
| this._resourceTrackingEnabled = false; |
| this.reset(); |
| }, |
| |
| reset: function() |
| { |
| this.closeVisibleResource(); |
| |
| delete this.currentQuery; |
| this.searchCanceled(); |
| |
| if (this._resources) { |
| var resourcesLength = this._resources.length; |
| for (var i = 0; i < resourcesLength; ++i) { |
| var resource = this._resources[i]; |
| |
| resource.warnings = 0; |
| resource.errors = 0; |
| |
| delete resource._resourcesView; |
| } |
| } |
| |
| WebInspector.AbstractTimelinePanel.prototype.reset.call(this); |
| |
| this.mainResourceLoadTime = -1; |
| this.mainResourceDOMContentTime = -1; |
| |
| this.viewsContainerElement.removeChildren(); |
| |
| this.summaryBar.reset(); |
| |
| if (this._resourceTrackingEnabled) { |
| this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable."); |
| this.enableToggleButton.toggled = true; |
| this.largerResourcesButton.visible = true; |
| this.sortingSelectElement.removeStyleClass("hidden"); |
| this.panelEnablerView.visible = false; |
| } else { |
| this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable."); |
| this.enableToggleButton.toggled = false; |
| this.largerResourcesButton.visible = false; |
| this.sortingSelectElement.addStyleClass("hidden"); |
| this.panelEnablerView.visible = true; |
| } |
| }, |
| |
| addResource: function(resource) |
| { |
| this._resources.push(resource); |
| this.refreshResource(resource); |
| }, |
| |
| removeResource: function(resource) |
| { |
| if (this.visibleView === resource._resourcesView) |
| this.closeVisibleResource(); |
| |
| this.removeItem(resource); |
| |
| resource.warnings = 0; |
| resource.errors = 0; |
| |
| delete resource._resourcesView; |
| }, |
| |
| addMessageToResource: function(resource, msg) |
| { |
| if (!resource) |
| return; |
| |
| switch (msg.level) { |
| case WebInspector.ConsoleMessage.MessageLevel.Warning: |
| resource.warnings += msg.repeatDelta; |
| break; |
| case WebInspector.ConsoleMessage.MessageLevel.Error: |
| resource.errors += msg.repeatDelta; |
| break; |
| } |
| |
| if (!this.currentQuery && resource._itemsTreeElement) |
| resource._itemsTreeElement.updateErrorsAndWarnings(); |
| |
| var view = this.resourceViewForResource(resource); |
| if (view.addMessage) |
| view.addMessage(msg); |
| }, |
| |
| clearMessages: function() |
| { |
| var resourcesLength = this._resources.length; |
| for (var i = 0; i < resourcesLength; ++i) { |
| var resource = this._resources[i]; |
| resource.warnings = 0; |
| resource.errors = 0; |
| |
| if (!this.currentQuery && resource._itemsTreeElement) |
| resource._itemsTreeElement.updateErrorsAndWarnings(); |
| |
| var view = resource._resourcesView; |
| if (!view || !view.clearMessages) |
| continue; |
| view.clearMessages(); |
| } |
| }, |
| |
| refreshResource: function(resource) |
| { |
| this.refreshItem(resource); |
| }, |
| |
| recreateViewForResourceIfNeeded: function(resource) |
| { |
| if (!resource || !resource._resourcesView) |
| return; |
| |
| var newView = this._createResourceView(resource); |
| if (newView.__proto__ === resource._resourcesView.__proto__) |
| return; |
| |
| resource.warnings = 0; |
| resource.errors = 0; |
| |
| if (!this.currentQuery && resource._itemsTreeElement) |
| resource._itemsTreeElement.updateErrorsAndWarnings(); |
| |
| var oldView = resource._resourcesView; |
| var oldViewParentNode = oldView.visible ? oldView.element.parentNode : null; |
| |
| resource._resourcesView.detach(); |
| delete resource._resourcesView; |
| |
| resource._resourcesView = newView; |
| |
| newView.headersVisible = oldView.headersVisible; |
| |
| if (oldViewParentNode) |
| newView.show(oldViewParentNode); |
| |
| WebInspector.panels.scripts.viewRecreated(oldView, newView); |
| }, |
| |
| canShowSourceLine: function(url, line) |
| { |
| return this._resourceTrackingEnabled && !!WebInspector.resourceForURL(url); |
| }, |
| |
| showSourceLine: function(url, line) |
| { |
| this.showResource(WebInspector.resourceForURL(url), line); |
| }, |
| |
| showResource: function(resource, line) |
| { |
| if (!resource) |
| return; |
| |
| this.containerElement.addStyleClass("viewing-resource"); |
| |
| if (this.visibleResource && this.visibleResource._resourcesView) |
| this.visibleResource._resourcesView.hide(); |
| |
| var view = this.resourceViewForResource(resource); |
| view.headersVisible = true; |
| view.show(this.viewsContainerElement); |
| |
| if (line) { |
| view.selectContentTab(); |
| if (view.revealLine) |
| view.revealLine(line); |
| if (view.highlightLine) |
| view.highlightLine(line); |
| } |
| |
| this.revealAndSelectItem(resource); |
| |
| this.visibleResource = resource; |
| |
| this.updateSidebarWidth(); |
| }, |
| |
| showView: function(view) |
| { |
| if (!view) |
| return; |
| this.showResource(view.resource); |
| }, |
| |
| closeVisibleResource: function() |
| { |
| this.containerElement.removeStyleClass("viewing-resource"); |
| this._updateDividersLabelBarPosition(); |
| |
| if (this.visibleResource && this.visibleResource._resourcesView) |
| this.visibleResource._resourcesView.hide(); |
| delete this.visibleResource; |
| |
| if (this._lastSelectedGraphTreeElement) |
| this._lastSelectedGraphTreeElement.select(true); |
| |
| this.updateSidebarWidth(); |
| }, |
| |
| resourceViewForResource: function(resource) |
| { |
| if (!resource) |
| return null; |
| if (!resource._resourcesView) |
| resource._resourcesView = this._createResourceView(resource); |
| return resource._resourcesView; |
| }, |
| |
| sourceFrameForResource: function(resource) |
| { |
| var view = this.resourceViewForResource(resource); |
| if (!view) |
| return null; |
| |
| if (!view.setupSourceFrameIfNeeded) |
| return null; |
| |
| // Setting up the source frame requires that we be attached. |
| if (!this.element.parentNode) |
| this.attach(); |
| |
| view.setupSourceFrameIfNeeded(); |
| return view.sourceFrame; |
| }, |
| |
| _sortResourcesIfNeeded: function() |
| { |
| this.sortItems(this.sortingFunction); |
| }, |
| |
| updateGraphDividersIfNeeded: function(force) |
| { |
| var proceed = WebInspector.AbstractTimelinePanel.prototype.updateGraphDividersIfNeeded.call(this, force); |
| |
| if (!proceed) |
| return; |
| |
| if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) { |
| // If our current sorting method starts at zero, that means it shows all |
| // resources starting at the same point, and so onLoad event and DOMContent |
| // event lines really wouldn't make much sense here, so don't render them. |
| // Additionally, if the calculator doesn't have the computePercentageFromEventTime |
| // function defined, we are probably sorting by size, and event times aren't relevant |
| // in this case. |
| return; |
| } |
| |
| if (this.mainResourceLoadTime !== -1) { |
| var percent = this.calculator.computePercentageFromEventTime(this.mainResourceLoadTime); |
| |
| var loadDivider = document.createElement("div"); |
| loadDivider.className = "resources-onload-divider"; |
| |
| var loadDividerPadding = document.createElement("div"); |
| loadDividerPadding.className = "resources-event-divider-padding"; |
| loadDividerPadding.style.left = percent + "%"; |
| loadDividerPadding.title = WebInspector.UIString("Load event fired"); |
| loadDividerPadding.appendChild(loadDivider); |
| |
| this.addEventDivider(loadDividerPadding); |
| } |
| |
| if (this.mainResourceDOMContentTime !== -1) { |
| var percent = this.calculator.computePercentageFromEventTime(this.mainResourceDOMContentTime); |
| |
| var domContentDivider = document.createElement("div"); |
| domContentDivider.className = "resources-ondomcontent-divider"; |
| |
| var domContentDividerPadding = document.createElement("div"); |
| domContentDividerPadding.className = "resources-event-divider-padding"; |
| domContentDividerPadding.style.left = percent + "%"; |
| domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired"); |
| domContentDividerPadding.appendChild(domContentDivider); |
| |
| this.addEventDivider(domContentDividerPadding); |
| } |
| }, |
| |
| _graphSelected: function(treeElement) |
| { |
| if (this._lastSelectedGraphTreeElement) |
| this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex; |
| |
| this._lastSelectedGraphTreeElement = treeElement; |
| |
| this.sortingSelectElement.removeChildren(); |
| for (var i = 0; i < treeElement.sortingOptions.length; ++i) { |
| var sortingOption = treeElement.sortingOptions[i]; |
| var option = document.createElement("option"); |
| option.label = sortingOption.name; |
| option.sortingFunction = sortingOption.sortingFunction; |
| option.calculator = sortingOption.calculator; |
| this.sortingSelectElement.appendChild(option); |
| } |
| |
| this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex; |
| this._changeSortingFunction(); |
| |
| this.closeVisibleResource(); |
| this.containerElement.scrollTop = 0; |
| }, |
| |
| _toggleLargerResources: function() |
| { |
| if (!this.itemsTreeElement._childrenListNode) |
| return; |
| |
| WebInspector.settings.resourcesLargeRows = !WebInspector.settings.resourcesLargeRows; |
| this._setLargerResources(this.itemsTreeElement.smallChildren); |
| }, |
| |
| _setLargerResources: function(enabled) |
| { |
| this.largerResourcesButton.toggled = enabled; |
| this.itemsTreeElement.smallChildren = !enabled; |
| if (!enabled) { |
| this.itemsGraphsElement.addStyleClass("small"); |
| this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows."); |
| this.adjustScrollPosition(); |
| } else { |
| this.itemsGraphsElement.removeStyleClass("small"); |
| this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); |
| } |
| }, |
| |
| _changeSortingFunction: function() |
| { |
| var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex]; |
| this.sortingFunction = selectedOption.sortingFunction; |
| this.calculator = this.summaryBar.calculator = selectedOption.calculator; |
| }, |
| |
| _createResourceView: function(resource) |
| { |
| switch (resource.category) { |
| case WebInspector.resourceCategories.documents: |
| case WebInspector.resourceCategories.stylesheets: |
| case WebInspector.resourceCategories.scripts: |
| case WebInspector.resourceCategories.xhr: |
| return new WebInspector.SourceView(resource); |
| case WebInspector.resourceCategories.images: |
| return new WebInspector.ImageView(resource); |
| case WebInspector.resourceCategories.fonts: |
| return new WebInspector.FontView(resource); |
| default: |
| return new WebInspector.ResourceView(resource); |
| } |
| }, |
| |
| setSidebarWidth: function(width) |
| { |
| if (this.visibleResource) { |
| this.containerElement.style.width = width + "px"; |
| this.sidebarElement.style.removeProperty("width"); |
| } else { |
| this.sidebarElement.style.width = width + "px"; |
| this.containerElement.style.removeProperty("width"); |
| } |
| |
| this.sidebarResizeElement.style.left = (width - 3) + "px"; |
| }, |
| |
| updateMainViewWidth: function(width) |
| { |
| this.viewsContainerElement.style.left = width + "px"; |
| |
| WebInspector.AbstractTimelinePanel.prototype.updateMainViewWidth.call(this, width); |
| this.resize(); |
| }, |
| |
| _enableResourceTracking: function() |
| { |
| if (this._resourceTrackingEnabled) |
| return; |
| this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled); |
| }, |
| |
| _toggleResourceTracking: function(optionalAlways) |
| { |
| if (this._resourceTrackingEnabled) { |
| this.largerResourcesButton.visible = false; |
| this.sortingSelectElement.visible = false; |
| WebInspector.resources = {}; |
| WebInspector.resourceURLMap = {}; |
| InspectorBackend.disableResourceTracking(true); |
| } else { |
| this.largerResourcesButton.visible = true; |
| this.sortingSelectElement.visible = true; |
| InspectorBackend.enableResourceTracking(!!optionalAlways); |
| } |
| }, |
| |
| get _resources() |
| { |
| return this.items; |
| }, |
| |
| searchIteratesOverViews: function() |
| { |
| return true; |
| } |
| } |
| |
| WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype; |
| |
| WebInspector.getResourceContent = function(identifier, callback) |
| { |
| InspectorBackend.getResourceContent(WebInspector.Callback.wrap(callback), identifier); |
| } |
| |
| WebInspector.didGetResourceContent = WebInspector.Callback.processCallback; |
| |
| WebInspector.ResourceTimeCalculator = function(startAtZero) |
| { |
| WebInspector.AbstractTimelineCalculator.call(this); |
| this.startAtZero = startAtZero; |
| } |
| |
| WebInspector.ResourceTimeCalculator.prototype = { |
| computeSummaryValues: function(resources) |
| { |
| var resourcesByCategory = {}; |
| var resourcesLength = resources.length; |
| for (var i = 0; i < resourcesLength; ++i) { |
| var resource = resources[i]; |
| if (!(resource.category.name in resourcesByCategory)) |
| resourcesByCategory[resource.category.name] = []; |
| resourcesByCategory[resource.category.name].push(resource); |
| } |
| |
| var earliestStart; |
| var latestEnd; |
| var categoryValues = {}; |
| for (var category in resourcesByCategory) { |
| resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime); |
| categoryValues[category] = 0; |
| |
| var segment = {start: -1, end: -1}; |
| |
| var categoryResources = resourcesByCategory[category]; |
| var resourcesLength = categoryResources.length; |
| for (var i = 0; i < resourcesLength; ++i) { |
| var resource = categoryResources[i]; |
| if (resource.startTime === -1 || resource.endTime === -1) |
| continue; |
| |
| if (typeof earliestStart === "undefined") |
| earliestStart = resource.startTime; |
| else |
| earliestStart = Math.min(earliestStart, resource.startTime); |
| |
| if (typeof latestEnd === "undefined") |
| latestEnd = resource.endTime; |
| else |
| latestEnd = Math.max(latestEnd, resource.endTime); |
| |
| if (resource.startTime <= segment.end) { |
| segment.end = Math.max(segment.end, resource.endTime); |
| continue; |
| } |
| |
| categoryValues[category] += segment.end - segment.start; |
| |
| segment.start = resource.startTime; |
| segment.end = resource.endTime; |
| } |
| |
| // Add the last segment |
| categoryValues[category] += segment.end - segment.start; |
| } |
| |
| return {categoryValues: categoryValues, total: latestEnd - earliestStart}; |
| }, |
| |
| computeBarGraphPercentages: function(resource) |
| { |
| if (resource.startTime !== -1) |
| var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100; |
| else |
| var start = 0; |
| |
| if (resource.responseReceivedTime !== -1) |
| var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100; |
| else |
| var middle = (this.startAtZero ? start : 100); |
| |
| if (resource.endTime !== -1) |
| var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100; |
| else |
| var end = (this.startAtZero ? middle : 100); |
| |
| if (this.startAtZero) { |
| end -= start; |
| middle -= start; |
| start = 0; |
| } |
| |
| return {start: start, middle: middle, end: end}; |
| }, |
| |
| computePercentageFromEventTime: function(eventTime) |
| { |
| // This function computes a percentage in terms of the total loading time |
| // of a specific event. If startAtZero is set, then this is useless, and we |
| // want to return 0. |
| if (eventTime !== -1 && !this.startAtZero) |
| return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100; |
| |
| return 0; |
| }, |
| |
| computeBarGraphLabels: function(resource) |
| { |
| var rightLabel = ""; |
| if (resource.responseReceivedTime !== -1 && resource.endTime !== -1) |
| rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime); |
| |
| var hasLatency = resource.latency > 0; |
| if (hasLatency) |
| var leftLabel = this.formatValue(resource.latency); |
| else |
| var leftLabel = rightLabel; |
| |
| if (hasLatency && rightLabel) { |
| var total = this.formatValue(resource.duration); |
| var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total); |
| } else if (hasLatency) |
| var tooltip = WebInspector.UIString("%s latency", leftLabel); |
| else if (rightLabel) |
| var tooltip = WebInspector.UIString("%s download", rightLabel); |
| |
| if (resource.cached) |
| tooltip = WebInspector.UIString("%s (from cache)", tooltip); |
| |
| return {left: leftLabel, right: rightLabel, tooltip: tooltip}; |
| }, |
| |
| updateBoundaries: function(resource) |
| { |
| var didChange = false; |
| |
| var lowerBound; |
| if (this.startAtZero) |
| lowerBound = 0; |
| else |
| lowerBound = this._lowerBound(resource); |
| |
| if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) { |
| this.minimumBoundary = lowerBound; |
| didChange = true; |
| } |
| |
| var upperBound = this._upperBound(resource); |
| if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) { |
| this.maximumBoundary = upperBound; |
| didChange = true; |
| } |
| |
| return didChange; |
| }, |
| |
| formatValue: function(value) |
| { |
| return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); |
| }, |
| |
| _lowerBound: function(resource) |
| { |
| return 0; |
| }, |
| |
| _upperBound: function(resource) |
| { |
| return 0; |
| } |
| } |
| |
| WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype; |
| |
| WebInspector.ResourceTransferTimeCalculator = function() |
| { |
| WebInspector.ResourceTimeCalculator.call(this, false); |
| } |
| |
| WebInspector.ResourceTransferTimeCalculator.prototype = { |
| formatValue: function(value) |
| { |
| return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); |
| }, |
| |
| _lowerBound: function(resource) |
| { |
| return resource.startTime; |
| }, |
| |
| _upperBound: function(resource) |
| { |
| return resource.endTime; |
| } |
| } |
| |
| WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype; |
| |
| WebInspector.ResourceTransferDurationCalculator = function() |
| { |
| WebInspector.ResourceTimeCalculator.call(this, true); |
| } |
| |
| WebInspector.ResourceTransferDurationCalculator.prototype = { |
| formatValue: function(value) |
| { |
| return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); |
| }, |
| |
| _upperBound: function(resource) |
| { |
| return resource.duration; |
| } |
| } |
| |
| WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype; |
| |
| WebInspector.ResourceTransferSizeCalculator = function() |
| { |
| WebInspector.AbstractTimelineCalculator.call(this); |
| } |
| |
| WebInspector.ResourceTransferSizeCalculator.prototype = { |
| computeBarGraphLabels: function(resource) |
| { |
| var networkBytes = this._networkBytes(resource); |
| var resourceBytes = this._value(resource); |
| if (networkBytes && networkBytes !== resourceBytes) { |
| // Transferred size is not the same as reported resource length. |
| var networkBytesString = this.formatValue(networkBytes); |
| var left = networkBytesString; |
| var right = this.formatValue(resourceBytes); |
| var tooltip = right ? WebInspector.UIString("%s (%s transferred)", right, networkBytesString) : right; |
| } else { |
| var left = this.formatValue(resourceBytes); |
| var right = left; |
| var tooltip = left; |
| } |
| if (resource.cached) |
| tooltip = WebInspector.UIString("%s (from cache)", tooltip); |
| return {left: left, right: right, tooltip: tooltip}; |
| }, |
| |
| computeBarGraphPercentages: function(item) |
| { |
| const resourceBytesAsPercent = (this._value(item) / this.boundarySpan) * 100; |
| const networkBytesAsPercent = this._networkBytes(item) ? (this._networkBytes(item) / this.boundarySpan) * 100 : resourceBytesAsPercent; |
| return {start: 0, middle: networkBytesAsPercent, end: resourceBytesAsPercent}; |
| }, |
| |
| _value: function(resource) |
| { |
| return resource.resourceSize; |
| }, |
| |
| _networkBytes: function(resource) |
| { |
| return resource.transferSize; |
| }, |
| |
| formatValue: function(value) |
| { |
| return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector)); |
| } |
| } |
| |
| WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype; |
| |
| WebInspector.ResourceSidebarTreeElement = function(resource) |
| { |
| this.resource = resource; |
| |
| this.createIconElement(); |
| |
| WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource); |
| |
| this.refreshTitles(); |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.prototype = { |
| onattach: function() |
| { |
| WebInspector.SidebarTreeElement.prototype.onattach.call(this); |
| |
| this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); |
| this._listItemNode.draggable = true; |
| |
| // FIXME: should actually add handler to parent, to be resolved via |
| // https://bugs.webkit.org/show_bug.cgi?id=30227 |
| this._listItemNode.addEventListener("dragstart", this.ondragstart.bind(this), false); |
| this.updateErrorsAndWarnings(); |
| }, |
| |
| onselect: function() |
| { |
| WebInspector.panels.resources.showResource(this.resource); |
| }, |
| |
| ondblclick: function(event) |
| { |
| InjectedScriptAccess.getDefault().openInInspectedWindow(this.resource.url, function() {}); |
| }, |
| |
| ondragstart: function(event) { |
| event.dataTransfer.setData("text/plain", this.resource.url); |
| event.dataTransfer.setData("text/uri-list", this.resource.url + "\r\n"); |
| event.dataTransfer.effectAllowed = "copy"; |
| return true; |
| }, |
| |
| get mainTitle() |
| { |
| return this.resource.displayName; |
| }, |
| |
| set mainTitle(x) |
| { |
| // Do nothing. |
| }, |
| |
| get subtitle() |
| { |
| var subtitle = this.resource.displayDomain; |
| |
| if (this.resource.path && this.resource.lastPathComponent) { |
| var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent); |
| if (lastPathComponentIndex != -1) |
| subtitle += this.resource.path.substring(0, lastPathComponentIndex); |
| } |
| |
| return subtitle; |
| }, |
| |
| set subtitle(x) |
| { |
| // Do nothing. |
| }, |
| |
| get selectable() |
| { |
| return WebInspector.panels.resources.isCategoryVisible(this.resource.category.name); |
| }, |
| |
| createIconElement: function() |
| { |
| var previousIconElement = this.iconElement; |
| |
| if (this.resource.category === WebInspector.resourceCategories.images) { |
| var previewImage = document.createElement("img"); |
| previewImage.className = "image-resource-icon-preview"; |
| previewImage.src = this.resource.url; |
| |
| this.iconElement = document.createElement("div"); |
| this.iconElement.className = "icon"; |
| this.iconElement.appendChild(previewImage); |
| } else { |
| this.iconElement = document.createElement("img"); |
| this.iconElement.className = "icon"; |
| } |
| |
| if (previousIconElement) |
| previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement); |
| }, |
| |
| refresh: function() |
| { |
| this.refreshTitles(); |
| |
| if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) { |
| this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+"); |
| this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); |
| |
| this.createIconElement(); |
| } |
| |
| this.tooltip = this.resource.url; |
| }, |
| |
| resetBubble: function() |
| { |
| this.bubbleText = ""; |
| this.bubbleElement.removeStyleClass("search-matches"); |
| this.bubbleElement.removeStyleClass("warning"); |
| this.bubbleElement.removeStyleClass("error"); |
| }, |
| |
| set searchMatches(matches) |
| { |
| this.resetBubble(); |
| |
| if (!matches) |
| return; |
| |
| this.bubbleText = matches; |
| this.bubbleElement.addStyleClass("search-matches"); |
| }, |
| |
| updateErrorsAndWarnings: function() |
| { |
| this.resetBubble(); |
| |
| if (this.resource.warnings || this.resource.errors) |
| this.bubbleText = (this.resource.warnings + this.resource.errors); |
| |
| if (this.resource.warnings) |
| this.bubbleElement.addStyleClass("warning"); |
| |
| if (this.resource.errors) |
| this.bubbleElement.addStyleClass("error"); |
| } |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b) |
| { |
| return WebInspector.Resource.CompareByStartTime(a.resource, b.resource) |
| || WebInspector.Resource.CompareByEndTime(a.resource, b.resource) |
| || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource); |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b) |
| { |
| return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource) |
| || WebInspector.Resource.CompareByStartTime(a.resource, b.resource) |
| || WebInspector.Resource.CompareByEndTime(a.resource, b.resource); |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b) |
| { |
| return WebInspector.Resource.CompareByEndTime(a.resource, b.resource) |
| || WebInspector.Resource.CompareByStartTime(a.resource, b.resource) |
| || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource); |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b) |
| { |
| return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource); |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b) |
| { |
| return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource); |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b) |
| { |
| return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource); |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize = function(a, b) |
| { |
| return -1 * WebInspector.Resource.CompareByTransferSize(a.resource, b.resource); |
| } |
| |
| WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; |
| |
| WebInspector.ResourceGraph = function(resource) |
| { |
| this.resource = resource; |
| |
| this._graphElement = document.createElement("div"); |
| this._graphElement.className = "resources-graph-side"; |
| this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false); |
| |
| if (resource.cached) |
| this._graphElement.addStyleClass("resource-cached"); |
| |
| this._barAreaElement = document.createElement("div"); |
| this._barAreaElement.className = "resources-graph-bar-area hidden"; |
| this._graphElement.appendChild(this._barAreaElement); |
| |
| this._barLeftElement = document.createElement("div"); |
| this._barLeftElement.className = "resources-graph-bar waiting"; |
| this._barAreaElement.appendChild(this._barLeftElement); |
| |
| this._barRightElement = document.createElement("div"); |
| this._barRightElement.className = "resources-graph-bar"; |
| this._barAreaElement.appendChild(this._barRightElement); |
| |
| this._labelLeftElement = document.createElement("div"); |
| this._labelLeftElement.className = "resources-graph-label waiting"; |
| this._barAreaElement.appendChild(this._labelLeftElement); |
| |
| this._labelRightElement = document.createElement("div"); |
| this._labelRightElement.className = "resources-graph-label"; |
| this._barAreaElement.appendChild(this._labelRightElement); |
| |
| this._graphElement.addStyleClass("resources-category-" + resource.category.name); |
| } |
| |
| WebInspector.ResourceGraph.prototype = { |
| get graphElement() |
| { |
| return this._graphElement; |
| }, |
| |
| refreshLabelPositions: function() |
| { |
| this._labelLeftElement.style.removeProperty("left"); |
| this._labelLeftElement.style.removeProperty("right"); |
| this._labelLeftElement.removeStyleClass("before"); |
| this._labelLeftElement.removeStyleClass("hidden"); |
| |
| this._labelRightElement.style.removeProperty("left"); |
| this._labelRightElement.style.removeProperty("right"); |
| this._labelRightElement.removeStyleClass("after"); |
| this._labelRightElement.removeStyleClass("hidden"); |
| |
| const labelPadding = 10; |
| const barRightElementOffsetWidth = this._barRightElement.offsetWidth; |
| const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth; |
| |
| if (this._isBarOpaqueAtLeft) { |
| var leftBarWidth = barLeftElementOffsetWidth - labelPadding; |
| var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding; |
| } else { |
| var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding; |
| var rightBarWidth = barRightElementOffsetWidth - labelPadding; |
| } |
| |
| const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth; |
| const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth; |
| |
| const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth); |
| const labelAfter = (labelRightElementOffsetWidth > rightBarWidth); |
| const graphElementOffsetWidth = this._graphElement.offsetWidth; |
| |
| if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10)) |
| var leftHidden = true; |
| |
| if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10)) |
| var rightHidden = true; |
| |
| if (barLeftElementOffsetWidth == barRightElementOffsetWidth) { |
| // The left/right label data are the same, so a before/after label can be replaced by an on-bar label. |
| if (labelBefore && !labelAfter) |
| leftHidden = true; |
| else if (labelAfter && !labelBefore) |
| rightHidden = true; |
| } |
| |
| if (labelBefore) { |
| if (leftHidden) |
| this._labelLeftElement.addStyleClass("hidden"); |
| this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%"); |
| this._labelLeftElement.addStyleClass("before"); |
| } else { |
| this._labelLeftElement.style.setProperty("left", this._percentages.start + "%"); |
| this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%"); |
| } |
| |
| if (labelAfter) { |
| if (rightHidden) |
| this._labelRightElement.addStyleClass("hidden"); |
| this._labelRightElement.style.setProperty("left", this._percentages.end + "%"); |
| this._labelRightElement.addStyleClass("after"); |
| } else { |
| this._labelRightElement.style.setProperty("left", this._percentages.middle + "%"); |
| this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%"); |
| } |
| }, |
| |
| refresh: function(calculator, isBarOpaqueAtLeft) |
| { |
| var percentages = calculator.computeBarGraphPercentages(this.resource); |
| var labels = calculator.computeBarGraphLabels(this.resource); |
| |
| this._percentages = percentages; |
| |
| this._barAreaElement.removeStyleClass("hidden"); |
| |
| if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) { |
| this._graphElement.removeMatchingStyleClasses("resources-category-\\w+"); |
| this._graphElement.addStyleClass("resources-category-" + this.resource.category.name); |
| } |
| |
| this._barLeftElement.style.setProperty("left", percentages.start + "%"); |
| this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); |
| |
| if (!isBarOpaqueAtLeft) { |
| this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); |
| this._barRightElement.style.setProperty("left", percentages.middle + "%"); |
| |
| if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) { |
| this._barLeftElement.addStyleClass("waiting"); |
| this._barRightElement.removeStyleClass("waiting-right"); |
| this._labelLeftElement.addStyleClass("waiting"); |
| this._labelRightElement.removeStyleClass("waiting-right"); |
| } |
| } else { |
| this._barLeftElement.style.setProperty("right", (100 - percentages.middle) + "%"); |
| this._barRightElement.style.setProperty("left", percentages.start + "%"); |
| |
| if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) { |
| this._barLeftElement.removeStyleClass("waiting"); |
| this._barRightElement.addStyleClass("waiting-right"); |
| this._labelLeftElement.removeStyleClass("waiting"); |
| this._labelRightElement.addStyleClass("waiting-right"); |
| } |
| } |
| |
| this._isBarOpaqueAtLeft = isBarOpaqueAtLeft; |
| |
| this._labelLeftElement.textContent = labels.left; |
| this._labelRightElement.textContent = labels.right; |
| |
| var tooltip = (labels.tooltip || ""); |
| this._barLeftElement.title = tooltip; |
| this._labelLeftElement.title = tooltip; |
| this._labelRightElement.title = tooltip; |
| this._barRightElement.title = tooltip; |
| } |
| } |