| /* |
| * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * 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.NetworkPanel = function() |
| { |
| WebInspector.Panel.call(this, "network"); |
| |
| this.createSidebar(); |
| this.sidebarElement.className = "network-sidebar"; |
| |
| this._resources = []; |
| this._resourcesById = {}; |
| this._resourcesByURL = {}; |
| this._staleResources = []; |
| this._resourceGridNodes = {}; |
| this._mainResourceLoadTime = -1; |
| this._mainResourceDOMContentTime = -1; |
| this._hiddenCategories = {}; |
| |
| this._categories = WebInspector.resourceCategories; |
| |
| this.containerElement = document.createElement("div"); |
| this.containerElement.id = "network-container"; |
| this.sidebarElement.appendChild(this.containerElement); |
| |
| this._viewsContainerElement = document.createElement("div"); |
| this._viewsContainerElement.id = "network-views"; |
| this._viewsContainerElement.className = "hidden"; |
| this.element.appendChild(this._viewsContainerElement); |
| |
| this._closeButtonElement = document.createElement("button"); |
| this._closeButtonElement.id = "network-close-button"; |
| this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false); |
| this._viewsContainerElement.appendChild(this._closeButtonElement); |
| |
| this._createSortingFunctions(); |
| this._createTable(); |
| this._createTimelineGrid(); |
| this._createStatusbarButtons(); |
| this._createFilterStatusBarItems(); |
| this._createSummaryBar(); |
| |
| if (!WebInspector.settings.resourcesLargeRows) |
| this._setLargerResources(WebInspector.settings.resourcesLargeRows); |
| |
| this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true); |
| // Enable faster hint. |
| this._popoverHelper.setTimeout(100); |
| |
| this.calculator = new WebInspector.NetworkTransferTimeCalculator(); |
| this._filter(this._filterAllElement, false); |
| |
| this._toggleGridMode(); |
| |
| WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this); |
| WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._onResourceUpdated, this); |
| WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceUpdated, this); |
| WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.MainResourceCommitLoad, this._onMainResourceCommitLoad, this); |
| } |
| |
| WebInspector.NetworkPanel.prototype = { |
| get toolbarItemLabel() |
| { |
| return WebInspector.UIString("Network"); |
| }, |
| |
| get statusBarItems() |
| { |
| return [this._largerResourcesButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement]; |
| }, |
| |
| isCategoryVisible: function(categoryName) |
| { |
| return true; |
| }, |
| |
| elementsToRestoreScrollPositionsFor: function() |
| { |
| return [this.containerElement, this._dataGrid.scrollContainer]; |
| }, |
| |
| resize: function() |
| { |
| WebInspector.Panel.prototype.resize.call(this); |
| this._dataGrid.updateWidths(); |
| this._positionSummaryBar(); |
| }, |
| |
| updateSidebarWidth: function(width) |
| { |
| if (!this._viewingResourceMode) |
| return; |
| WebInspector.Panel.prototype.updateSidebarWidth.call(this, width); |
| }, |
| |
| updateMainViewWidth: function(width) |
| { |
| this._viewsContainerElement.style.left = width + "px"; |
| }, |
| |
| handleShortcut: function(event) |
| { |
| if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { |
| this._toggleGridMode(); |
| event.handled = true; |
| } |
| }, |
| |
| _positionSummaryBar: function() |
| { |
| // Position the total bar. |
| |
| var fillerRow = this._dataGrid.dataTableBody.lastChild; |
| if (this._summaryBarElement.parentElement !== this.element && fillerRow.offsetHeight > 0) { |
| // Glue status to bottom. |
| if (this._summaryBarRowNode) { |
| this._dataGrid.removeChild(this._summaryBarRowNode); |
| delete this._summaryBarRowNode; |
| } |
| this._summaryBarElement.addStyleClass("network-summary-bar-bottom"); |
| this.element.appendChild(this._summaryBarElement); |
| this._dataGrid.element.style.bottom = "20px"; |
| return; |
| } |
| |
| if (!this._summaryBarRowNode && !fillerRow.offsetHeight) { |
| // Glue status to table. |
| this._summaryBarRowNode = new WebInspector.NetworkTotalGridNode(this._summaryBarElement); |
| this._summaryBarElement.removeStyleClass("network-summary-bar-bottom"); |
| this._dataGrid.appendChild(this._summaryBarRowNode); |
| this._dataGrid.element.style.bottom = 0; |
| this._sortItems(); |
| } |
| this._updateOffscreenRows(); |
| }, |
| |
| _resetSummaryBar: function() |
| { |
| delete this._summaryBarRowNode; |
| this._summaryBarElement.parentElement.removeChild(this._summaryBarElement); |
| this._updateSummaryBar(); |
| }, |
| |
| _createTimelineGrid: function() |
| { |
| this._timelineGrid = new WebInspector.TimelineGrid(); |
| this._timelineGrid.element.addStyleClass("network-timeline-grid"); |
| this._dataGrid.element.appendChild(this._timelineGrid.element); |
| }, |
| |
| _createTable: function() |
| { |
| var columns = {name: {}, method: {}, status: {}, type: {}, size: {}, time: {}, timeline: {}}; |
| columns.name.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path")); |
| columns.name.sortable = true; |
| columns.name.width = "20%"; |
| columns.name.disclosure = true; |
| |
| columns.method.title = WebInspector.UIString("Method"); |
| columns.method.sortable = true; |
| columns.method.width = "7%"; |
| |
| columns.status.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text")); |
| columns.status.sortable = true; |
| columns.status.width = "8%"; |
| |
| columns.type.title = WebInspector.UIString("Type"); |
| columns.type.sortable = true; |
| columns.type.width = "10%"; |
| |
| columns.size.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Transfer")); |
| columns.size.sortable = true; |
| columns.size.width = "10%"; |
| columns.size.aligned = "right"; |
| |
| columns.time.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency")); |
| columns.time.sortable = true; |
| columns.time.width = "10%"; |
| columns.time.aligned = "right"; |
| |
| columns.timeline.title = ""; |
| columns.timeline.sortable = false; |
| columns.timeline.width = "37%"; |
| columns.timeline.sort = "ascending"; |
| |
| this._dataGrid = new WebInspector.DataGrid(columns); |
| this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true); |
| this.containerElement.appendChild(this._dataGrid.element); |
| this._dataGrid.addEventListener("sorting changed", this._sortItems, this); |
| this._dataGrid.addEventListener("width changed", this._updateDividersIfNeeded, this); |
| this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this)); |
| |
| this._patchTimelineHeader(); |
| }, |
| |
| _makeHeaderFragment: function(title, subtitle) |
| { |
| var fragment = document.createDocumentFragment(); |
| fragment.appendChild(document.createTextNode(title)); |
| var subtitleDiv = document.createElement("div"); |
| subtitleDiv.className = "network-header-subtitle"; |
| subtitleDiv.textContent = subtitle; |
| fragment.appendChild(subtitleDiv); |
| return fragment; |
| }, |
| |
| _patchTimelineHeader: function() |
| { |
| var timelineSorting = document.createElement("select"); |
| |
| var option = document.createElement("option"); |
| option.value = "startTime"; |
| option.label = WebInspector.UIString("Timeline"); |
| timelineSorting.appendChild(option); |
| |
| option = document.createElement("option"); |
| option.value = "startTime"; |
| option.label = WebInspector.UIString("Start Time"); |
| timelineSorting.appendChild(option); |
| |
| option = document.createElement("option"); |
| option.value = "responseTime"; |
| option.label = WebInspector.UIString("Response Time"); |
| timelineSorting.appendChild(option); |
| |
| option = document.createElement("option"); |
| option.value = "endTime"; |
| option.label = WebInspector.UIString("End Time"); |
| timelineSorting.appendChild(option); |
| |
| option = document.createElement("option"); |
| option.value = "duration"; |
| option.label = WebInspector.UIString("Duration"); |
| timelineSorting.appendChild(option); |
| |
| option = document.createElement("option"); |
| option.value = "latency"; |
| option.label = WebInspector.UIString("Latency"); |
| timelineSorting.appendChild(option); |
| |
| var header = this._dataGrid.headerTableHeader("timeline"); |
| header.replaceChild(timelineSorting, header.firstChild); |
| |
| timelineSorting.addEventListener("click", function(event) { event.stopPropagation() }, false); |
| timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false); |
| this._timelineSortSelector = timelineSorting; |
| }, |
| |
| _createSortingFunctions: function() |
| { |
| this._sortingFunctions = {}; |
| this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator; |
| this._sortingFunctions.method = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "method", false); |
| this._sortingFunctions.status = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "statusCode", false); |
| this._sortingFunctions.type = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "mimeType", false); |
| this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator; |
| this._sortingFunctions.time = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", false); |
| this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false); |
| this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false); |
| this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "endTime", false); |
| this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "responseReceivedTime", false); |
| this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", true); |
| this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "latency", true); |
| |
| var timeCalculator = new WebInspector.NetworkTransferTimeCalculator(); |
| var durationCalculator = new WebInspector.NetworkTransferDurationCalculator(); |
| |
| this._calculators = {}; |
| this._calculators.timeline = timeCalculator; |
| this._calculators.startTime = timeCalculator; |
| this._calculators.endTime = timeCalculator; |
| this._calculators.responseTime = timeCalculator; |
| this._calculators.duration = durationCalculator; |
| this._calculators.latency = durationCalculator; |
| }, |
| |
| _sortItems: function() |
| { |
| var columnIdentifier = this._dataGrid.sortColumnIdentifier; |
| if (columnIdentifier === "timeline") { |
| this._sortByTimeline(); |
| return; |
| } |
| var sortingFunction = this._sortingFunctions[columnIdentifier]; |
| if (!sortingFunction) |
| return; |
| |
| this._dataGrid.sortNodes(sortingFunction, this._dataGrid.sortOrder === "descending"); |
| this._timelineSortSelector.selectedIndex = 0; |
| this._updateOffscreenRows(); |
| }, |
| |
| _sortByTimeline: function() |
| { |
| var selectedIndex = this._timelineSortSelector.selectedIndex; |
| if (!selectedIndex) |
| selectedIndex = 1; // Sort by start time by default. |
| var selectedOption = this._timelineSortSelector[selectedIndex]; |
| var value = selectedOption.value; |
| |
| var sortingFunction = this._sortingFunctions[value]; |
| this._dataGrid.sortNodes(sortingFunction); |
| this.calculator = this._calculators[value]; |
| if (this.calculator.startAtZero) |
| this._timelineGrid.hideEventDividers(); |
| else |
| this._timelineGrid.showEventDividers(); |
| this._dataGrid.markColumnAsSortedBy("timeline", "ascending"); |
| this._updateOffscreenRows(); |
| }, |
| |
| _createFilterStatusBarItems: function() |
| { |
| var filterBarElement = document.createElement("div"); |
| filterBarElement.className = "scope-bar status-bar-item"; |
| filterBarElement.id = "network-filter"; |
| |
| function createFilterElement(category, label) |
| { |
| var categoryElement = document.createElement("li"); |
| categoryElement.category = category; |
| categoryElement.className = category; |
| categoryElement.appendChild(document.createTextNode(label)); |
| categoryElement.addEventListener("click", this._updateFilter.bind(this), false); |
| filterBarElement.appendChild(categoryElement); |
| |
| return categoryElement; |
| } |
| |
| this._filterAllElement = createFilterElement.call(this, "all", WebInspector.UIString("All")); |
| |
| // Add a divider |
| var dividerElement = document.createElement("div"); |
| dividerElement.addStyleClass("scope-bar-divider"); |
| filterBarElement.appendChild(dividerElement); |
| |
| for (var category in this._categories) |
| createFilterElement.call(this, category, this._categories[category].title); |
| this._filterBarElement = filterBarElement; |
| }, |
| |
| _createSummaryBar: function() |
| { |
| this._summaryBarElement = document.createElement("div"); |
| this._summaryBarElement.className = "network-summary-bar"; |
| this.containerElement.appendChild(this._summaryBarElement); |
| }, |
| |
| _updateSummaryBar: function() |
| { |
| this._positionSummaryBar(); // Grid is growing. |
| var numRequests = this._resources.length; |
| |
| if (!numRequests) { |
| if (this._summaryBarElement._isDisplayingWarning) |
| return; |
| this._summaryBarElement._isDisplayingWarning = true; |
| |
| var img = document.createElement("img"); |
| img.src = "Images/warningIcon.png"; |
| this._summaryBarElement.removeChildren(); |
| this._summaryBarElement.appendChild(img); |
| this._summaryBarElement.appendChild(document.createTextNode(" ")); |
| this._summaryBarElement.appendChild(document.createTextNode( |
| WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity."))); |
| return; |
| } |
| delete this._summaryBarElement._isDisplayingWarning; |
| |
| var transferSize = 0; |
| var baseTime = -1; |
| var maxTime = -1; |
| for (var i = 0; i < this._resources.length; ++i) { |
| var resource = this._resources[i]; |
| transferSize += (resource.cached || !resource.transferSize) ? 0 : resource.transferSize; |
| if (resource.isMainResource) |
| baseTime = resource.startTime; |
| if (resource.endTime > maxTime) |
| maxTime = resource.endTime; |
| } |
| var text = String.sprintf(WebInspector.UIString("%d requests"), numRequests); |
| text += " \u2758 " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize)); |
| if (baseTime !== -1 && this._mainResourceLoadTime !== -1 && this._mainResourceDOMContentTime !== -1 && this._mainResourceDOMContentTime > baseTime) { |
| text += " \u2758 " + String.sprintf(WebInspector.UIString("%s (onload: %s, DOMContentLoaded: %s)"), |
| Number.secondsToString(maxTime - baseTime), |
| Number.secondsToString(this._mainResourceLoadTime - baseTime), |
| Number.secondsToString(this._mainResourceDOMContentTime - baseTime)); |
| } |
| this._summaryBarElement.textContent = text; |
| }, |
| |
| _showCategory: function(category) |
| { |
| this._dataGrid.element.addStyleClass("filter-" + category); |
| delete this._hiddenCategories[category]; |
| }, |
| |
| _hideCategory: function(category) |
| { |
| this._dataGrid.element.removeStyleClass("filter-" + category); |
| this._hiddenCategories[category] = true; |
| }, |
| |
| _updateFilter: function(e) |
| { |
| var isMac = WebInspector.isMac(); |
| var selectMultiple = false; |
| if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) |
| selectMultiple = true; |
| if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) |
| selectMultiple = true; |
| |
| this._filter(e.target, selectMultiple); |
| this._positionSummaryBar(); |
| }, |
| |
| _filter: function(target, selectMultiple) |
| { |
| function unselectAll() |
| { |
| for (var i = 0; i < this._filterBarElement.childNodes.length; ++i) { |
| var child = this._filterBarElement.childNodes[i]; |
| if (!child.category) |
| continue; |
| |
| child.removeStyleClass("selected"); |
| this._hideCategory(child.category); |
| } |
| } |
| |
| if (target.category === this._filterAllElement) { |
| if (target.hasStyleClass("selected")) { |
| // We can't unselect All, so we break early here |
| return; |
| } |
| |
| // If All wasn't selected, and now is, unselect everything else. |
| unselectAll.call(this); |
| } else { |
| // Something other than All is being selected, so we want to unselect All. |
| if (this._filterAllElement.hasStyleClass("selected")) { |
| this._filterAllElement.removeStyleClass("selected"); |
| this._hideCategory("all"); |
| } |
| } |
| |
| if (!selectMultiple) { |
| // If multiple selection is off, we want to unselect everything else |
| // and just select ourselves. |
| unselectAll.call(this); |
| |
| target.addStyleClass("selected"); |
| this._showCategory(target.category); |
| return; |
| } |
| |
| if (target.hasStyleClass("selected")) { |
| // If selectMultiple is turned on, and we were selected, we just |
| // want to unselect ourselves. |
| target.removeStyleClass("selected"); |
| this._hideCategory(target.category); |
| } else { |
| // If selectMultiple is turned on, and we weren't selected, we just |
| // want to select ourselves. |
| target.addStyleClass("selected"); |
| this._showCategory(target.category); |
| } |
| this._updateOffscreenRows(); |
| }, |
| |
| _scheduleRefresh: function() |
| { |
| if (this._needsRefresh) |
| return; |
| |
| this._needsRefresh = true; |
| |
| if (this.visible && !("_refreshTimeout" in this)) |
| this._refreshTimeout = setTimeout(this.refresh.bind(this), 500); |
| }, |
| |
| _updateDividersIfNeeded: function(force) |
| { |
| var timelineColumn = this._dataGrid.columns.timeline; |
| for (var i = 0; i < this._dataGrid.resizers.length; ++i) { |
| if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) { |
| // Position timline grid location. |
| this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left; |
| this._timelineGrid.element.style.right = "18px"; |
| } |
| } |
| |
| var proceed = true; |
| if (!this.visible) { |
| this._scheduleRefresh(); |
| proceed = false; |
| } else |
| proceed = this._timelineGrid.updateDividers(force, this.calculator); |
| |
| 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; |
| } |
| |
| this._timelineGrid.removeEventDividers(); |
| if (this._mainResourceLoadTime !== -1) { |
| var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime); |
| |
| var loadDivider = document.createElement("div"); |
| loadDivider.className = "network-event-divider network-red-divider"; |
| |
| var loadDividerPadding = document.createElement("div"); |
| loadDividerPadding.className = "network-event-divider-padding"; |
| loadDividerPadding.title = WebInspector.UIString("Load event fired"); |
| loadDividerPadding.appendChild(loadDivider); |
| loadDividerPadding.style.left = percent + "%"; |
| this._timelineGrid.addEventDivider(loadDividerPadding); |
| } |
| |
| if (this._mainResourceDOMContentTime !== -1) { |
| var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime); |
| |
| var domContentDivider = document.createElement("div"); |
| domContentDivider.className = "network-event-divider network-blue-divider"; |
| |
| var domContentDividerPadding = document.createElement("div"); |
| domContentDividerPadding.className = "network-event-divider-padding"; |
| domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired"); |
| domContentDividerPadding.appendChild(domContentDivider); |
| domContentDividerPadding.style.left = percent + "%"; |
| this._timelineGrid.addEventDivider(domContentDividerPadding); |
| } |
| }, |
| |
| _refreshIfNeeded: function() |
| { |
| if (this._needsRefresh) |
| this.refresh(); |
| }, |
| |
| _invalidateAllItems: function() |
| { |
| this._staleResources = this._resources.slice(); |
| }, |
| |
| get calculator() |
| { |
| return this._calculator; |
| }, |
| |
| set calculator(x) |
| { |
| if (!x || this._calculator === x) |
| return; |
| |
| this._calculator = x; |
| this._calculator.reset(); |
| |
| this._invalidateAllItems(); |
| this.refresh(); |
| }, |
| |
| _resourceGridNode: function(resource) |
| { |
| return this._resourceGridNodes[resource.identifier]; |
| }, |
| |
| revealAndSelectItem: function(resource) |
| { |
| var node = this._resourceGridNode(resource); |
| if (node) { |
| node.reveal(); |
| node.select(true); |
| } |
| }, |
| |
| addEventDivider: function(divider) |
| { |
| this._timelineGrid.addEventDivider(divider); |
| }, |
| |
| _createStatusbarButtons: function() |
| { |
| this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item"); |
| this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked.bind(this), false); |
| |
| this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); |
| this._clearButton.addEventListener("click", this._reset.bind(this), false); |
| |
| this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item"); |
| this._largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows; |
| this._largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false); |
| }, |
| |
| set mainResourceLoadTime(x) |
| { |
| if (this._mainResourceLoadTime === x) |
| return; |
| |
| this._mainResourceLoadTime = x || -1; |
| // Update the dividers to draw the new line |
| this._updateDividersIfNeeded(true); |
| }, |
| |
| set mainResourceDOMContentTime(x) |
| { |
| if (this._mainResourceDOMContentTime === x) |
| return; |
| |
| this._mainResourceDOMContentTime = x || -1; |
| this._updateDividersIfNeeded(true); |
| }, |
| |
| show: function() |
| { |
| WebInspector.Panel.prototype.show.call(this); |
| this._refreshIfNeeded(); |
| |
| if (this.visibleView) |
| this.visibleView.show(this._viewsContainerElement); |
| |
| this._dataGrid.updateWidths(); |
| this._positionSummaryBar(); |
| }, |
| |
| hide: function() |
| { |
| WebInspector.Panel.prototype.hide.call(this); |
| this._popoverHelper.hidePopup(); |
| }, |
| |
| get searchableViews() |
| { |
| var views = []; |
| return views; |
| }, |
| |
| searchMatchFound: function(view, matches) |
| { |
| this._resourceGridNode(view.resource).searchMatches = matches; |
| }, |
| |
| searchCanceled: function(startingNewSearch) |
| { |
| WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); |
| |
| if (startingNewSearch || !this._resources) |
| return; |
| }, |
| |
| performSearch: function(query) |
| { |
| WebInspector.Panel.prototype.performSearch.call(this, query); |
| }, |
| |
| refresh: function() |
| { |
| this._needsRefresh = false; |
| if ("_refreshTimeout" in this) { |
| clearTimeout(this._refreshTimeout); |
| delete this._refreshTimeout; |
| } |
| |
| var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow(); |
| var staleItemsLength = this._staleResources.length; |
| var boundariesChanged = false; |
| |
| for (var i = 0; i < staleItemsLength; ++i) { |
| var resource = this._staleResources[i]; |
| var node = this._resourceGridNode(resource); |
| if (!node) { |
| // Create the timeline tree element and graph. |
| node = new WebInspector.NetworkDataGridNode(this, resource); |
| this._resourceGridNodes[resource.identifier] = node; |
| this._dataGrid.appendChild(node); |
| } |
| node.refreshResource(); |
| |
| if (this.calculator.updateBoundaries(resource)) |
| boundariesChanged = true; |
| } |
| |
| if (boundariesChanged) { |
| // The boundaries changed, so all item graphs are stale. |
| this._invalidateAllItems(); |
| staleItemsLength = this._staleResources.length; |
| } |
| |
| for (var i = 0; i < staleItemsLength; ++i) |
| this._resourceGridNode(this._staleResources[i]).refreshGraph(this.calculator); |
| |
| this._staleResources = []; |
| this._sortItems(); |
| this._updateSummaryBar(); |
| this._dataGrid.updateWidths(); |
| |
| if (wasScrolledToLastRow) |
| this._dataGrid.scrollToLastRow(); |
| }, |
| |
| _onPreserveLogClicked: function(e) |
| { |
| this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled; |
| }, |
| |
| _reset: function() |
| { |
| this._popoverHelper.hidePopup(); |
| this._closeVisibleResource(); |
| |
| this._toggleGridMode(); |
| |
| // Begin reset timeline |
| if (this._calculator) |
| this._calculator.reset(); |
| |
| this._resources = []; |
| this._resourcesById = {}; |
| this._resourcesByURL = {}; |
| this._staleResources = []; |
| this._resourceGridNodes = {}; |
| |
| this._dataGrid.removeChildren(); |
| delete this._summaryBarRowNode; |
| this._updateDividersIfNeeded(true); |
| // End reset timeline. |
| |
| this._mainResourceLoadTime = -1; |
| this._mainResourceDOMContentTime = -1; |
| |
| this._viewsContainerElement.removeChildren(); |
| this._viewsContainerElement.appendChild(this._closeButtonElement); |
| this._resetSummaryBar(); |
| }, |
| |
| get resources() |
| { |
| return this._resources; |
| }, |
| |
| resourceById: function(id) |
| { |
| return this._resourcesById[id]; |
| }, |
| |
| _onResourceStarted: function(event) |
| { |
| this._appendResource(event.data); |
| }, |
| |
| _appendResource: function(resource) |
| { |
| this._resources.push(resource); |
| this._resourcesById[resource.identifier] = resource; |
| this._resourcesByURL[resource.url] = resource; |
| |
| // Pull all the redirects of the main resource upon commit load. |
| if (resource.redirects) { |
| for (var i = 0; i < resource.redirects.length; ++i) |
| this._refreshResource(resource.redirects[i]); |
| } |
| |
| this._refreshResource(resource); |
| }, |
| |
| _onResourceUpdated: function(event) |
| { |
| this._refreshResource(event.data); |
| }, |
| |
| _refreshResource: function(resource) |
| { |
| this._staleResources.push(resource); |
| this._scheduleRefresh(); |
| |
| var oldView = WebInspector.ResourceView.existingResourceViewForResource(resource); |
| if (!oldView) |
| return; |
| |
| if (WebInspector.ResourceView.resourceViewTypeMatchesResource(resource)) |
| return; |
| |
| var newView = WebInspector.ResourceView.recreateResourceView(resource); |
| if (this.visibleView === oldView) |
| this.visibleView = newView; |
| }, |
| |
| clear: function() |
| { |
| if (this._preserveLogToggle.toggled) |
| return; |
| this._reset(); |
| }, |
| |
| _onMainResourceCommitLoad: function() |
| { |
| if (this._preserveLogToggle.toggled) |
| return; |
| |
| this._reset(); |
| // Now resurrect the main resource along with all redirects that lead to it. |
| var resourcesToAppend = (WebInspector.mainResource.redirects || []).concat(WebInspector.mainResource); |
| resourcesToAppend.forEach(this._appendResource, this); |
| }, |
| |
| canShowSourceLine: function(url, line) |
| { |
| return !!this._resourcesByURL[url]; |
| }, |
| |
| showSourceLine: function(url, line) |
| { |
| this._showResource(this._resourcesByURL[url], line); |
| }, |
| |
| _showResource: function(resource, line) |
| { |
| if (!resource) |
| return; |
| |
| this._popoverHelper.hidePopup(); |
| |
| this._toggleViewingResourceMode(); |
| |
| if (this.visibleView) { |
| this.visibleView.detach(); |
| delete this.visibleView; |
| } |
| |
| var view = new WebInspector.NetworkItemView(resource); |
| view.show(this._viewsContainerElement); |
| this.visibleView = view; |
| |
| this.updateSidebarWidth(); |
| }, |
| |
| _closeVisibleResource: function() |
| { |
| this.element.removeStyleClass("viewing-resource"); |
| |
| if (this.visibleView) { |
| this.visibleView.detach(); |
| delete this.visibleView; |
| } |
| |
| if (this._lastSelectedGraphTreeElement) |
| this._lastSelectedGraphTreeElement.select(true); |
| |
| this.updateSidebarWidth(); |
| }, |
| |
| _toggleLargerResources: function() |
| { |
| WebInspector.settings.resourcesLargeRows = !WebInspector.settings.resourcesLargeRows; |
| this._setLargerResources(WebInspector.settings.resourcesLargeRows); |
| }, |
| |
| _setLargerResources: function(enabled) |
| { |
| this._largerResourcesButton.toggled = enabled; |
| if (!enabled) { |
| this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows."); |
| this._dataGrid.element.addStyleClass("small"); |
| this._timelineGrid.element.addStyleClass("small"); |
| this._viewsContainerElement.addStyleClass("small"); |
| } else { |
| this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); |
| this._dataGrid.element.removeStyleClass("small"); |
| this._timelineGrid.element.removeStyleClass("small"); |
| this._viewsContainerElement.removeStyleClass("small"); |
| } |
| this._positionSummaryBar(); |
| }, |
| |
| _getPopoverAnchor: function(element) |
| { |
| var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label"); |
| if (!anchor) |
| return null; |
| var resource = anchor.parentElement.resource; |
| return resource && resource.timing ? anchor : null; |
| }, |
| |
| _showPopover: function(anchor) |
| { |
| var resource = anchor.parentElement.resource; |
| var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource); |
| var popover = new WebInspector.Popover(tableElement); |
| popover.show(anchor); |
| return popover; |
| }, |
| |
| _toggleGridMode: function() |
| { |
| if (this._viewingResourceMode) { |
| this._viewingResourceMode = false; |
| this.element.removeStyleClass("viewing-resource"); |
| this._dataGrid.element.removeStyleClass("viewing-resource-mode"); |
| this._viewsContainerElement.addStyleClass("hidden"); |
| this.sidebarElement.style.right = 0; |
| this.sidebarElement.style.removeProperty("width"); |
| if (this._dataGrid.selectedNode) |
| this._dataGrid.selectedNode.selected = false; |
| } |
| |
| if (this._briefGrid) { |
| this._dataGrid.element.removeStyleClass("full-grid-mode"); |
| this._dataGrid.element.addStyleClass("brief-grid-mode"); |
| |
| this._dataGrid.hideColumn("method"); |
| this._dataGrid.hideColumn("status"); |
| this._dataGrid.hideColumn("type"); |
| this._dataGrid.hideColumn("size"); |
| this._dataGrid.hideColumn("time"); |
| |
| var widths = {}; |
| widths.name = 20; |
| widths.timeline = 80; |
| } else { |
| this._dataGrid.element.addStyleClass("full-grid-mode"); |
| this._dataGrid.element.removeStyleClass("brief-grid-mode"); |
| |
| this._dataGrid.showColumn("method"); |
| this._dataGrid.showColumn("status"); |
| this._dataGrid.showColumn("type"); |
| this._dataGrid.showColumn("size"); |
| this._dataGrid.showColumn("time"); |
| |
| var widths = {}; |
| widths.name = 20; |
| widths.method = 7; |
| widths.status = 8; |
| widths.type = 10; |
| widths.size = 10; |
| widths.time = 10; |
| widths.timeline = 37; |
| } |
| |
| this._dataGrid.showColumn("timeline"); |
| this._dataGrid.applyColumnWidthsMap(widths); |
| |
| }, |
| |
| _toggleViewingResourceMode: function() |
| { |
| if (this._viewingResourceMode) |
| return; |
| this._viewingResourceMode = true; |
| this._preservedColumnWidths = this._dataGrid.columnWidthsMap(); |
| |
| this.element.addStyleClass("viewing-resource"); |
| this._dataGrid.element.addStyleClass("viewing-resource-mode"); |
| this._dataGrid.element.removeStyleClass("full-grid-mode"); |
| this._dataGrid.element.removeStyleClass("brief-grid-mode"); |
| |
| this._dataGrid.hideColumn("method"); |
| this._dataGrid.hideColumn("status"); |
| this._dataGrid.hideColumn("type"); |
| this._dataGrid.hideColumn("size"); |
| this._dataGrid.hideColumn("time"); |
| this._dataGrid.hideColumn("timeline"); |
| |
| this._viewsContainerElement.removeStyleClass("hidden"); |
| this.updateSidebarWidth(200); |
| |
| var widths = {}; |
| widths.name = 100; |
| this._dataGrid.applyColumnWidthsMap(widths); |
| }, |
| |
| _contextMenu: function(event) |
| { |
| // createBlobURL is enabled conditionally, do not expose resource export if it's not available. |
| if (typeof window.webkitURL.createObjectURL !== "function" || !Preferences.resourceExportEnabled) |
| return; |
| |
| var contextMenu = new WebInspector.ContextMenu(); |
| var gridNode = this._dataGrid.dataGridNodeFromNode(event.target); |
| var resource = gridNode && gridNode._resource; |
| if (resource) |
| contextMenu.appendItem(WebInspector.UIString("Export to HAR"), this._exportResource.bind(this, resource)); |
| contextMenu.appendItem(WebInspector.UIString("Export all to HAR"), this._exportAll.bind(this)); |
| contextMenu.show(event); |
| }, |
| |
| _exportAll: function() |
| { |
| var harArchive = { |
| log: (new WebInspector.HARLog()).build() |
| } |
| InspectorFrontendHost.copyText(JSON.stringify(harArchive)); |
| }, |
| |
| _exportResource: function(resource) |
| { |
| var har = (new WebInspector.HAREntry(resource)).build(); |
| InspectorFrontendHost.copyText(JSON.stringify(har)); |
| }, |
| |
| _updateOffscreenRows: function(e) |
| { |
| var dataTableBody = this._dataGrid.dataTableBody; |
| var rows = dataTableBody.children; |
| var recordsCount = rows.length; |
| if (recordsCount < 2) |
| return; // Filler row only. |
| |
| var visibleTop = this._dataGrid.scrollContainer.scrollTop; |
| var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight; |
| |
| var rowHeight = 0; |
| |
| // Filler is at recordsCount - 1. |
| var unfilteredRowIndex = 0; |
| for (var i = 0; i < recordsCount - 1; ++i) { |
| var row = rows[i]; |
| // Don't touch summaty - quit instead. |
| if (this._summaryBarRowNode && row === this._summaryBarRowNode.element) |
| break; |
| |
| var dataGridNode = this._dataGrid.dataGridNodeFromNode(row); |
| if (dataGridNode.isFilteredOut()) { |
| row.removeStyleClass("offscreen"); |
| continue; |
| } |
| |
| if (!rowHeight) |
| rowHeight = row.offsetHeight; |
| |
| var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop; |
| if (rowIsVisible !== row.rowIsVisible) { |
| if (rowIsVisible) |
| row.removeStyleClass("offscreen"); |
| else |
| row.addStyleClass("offscreen"); |
| row.rowIsVisible = rowIsVisible; |
| } |
| unfilteredRowIndex++; |
| } |
| } |
| } |
| |
| WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype; |
| |
| WebInspector.NetworkBaseCalculator = function() |
| { |
| } |
| |
| WebInspector.NetworkBaseCalculator.prototype = { |
| computeSummaryValues: function(items) |
| { |
| var total = 0; |
| var categoryValues = {}; |
| |
| var itemsLength = items.length; |
| for (var i = 0; i < itemsLength; ++i) { |
| var item = items[i]; |
| var value = this._value(item); |
| if (typeof value === "undefined") |
| continue; |
| if (!(item.category.name in categoryValues)) |
| categoryValues[item.category.name] = 0; |
| categoryValues[item.category.name] += value; |
| total += value; |
| } |
| |
| return {categoryValues: categoryValues, total: total}; |
| }, |
| |
| computeBarGraphPercentages: function(item) |
| { |
| return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100}; |
| }, |
| |
| computeBarGraphLabels: function(item) |
| { |
| const label = this.formatValue(this._value(item)); |
| return {left: label, right: label, tooltip: label}; |
| }, |
| |
| get boundarySpan() |
| { |
| return this.maximumBoundary - this.minimumBoundary; |
| }, |
| |
| updateBoundaries: function(item) |
| { |
| this.minimumBoundary = 0; |
| |
| var value = this._value(item); |
| if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) { |
| this.maximumBoundary = value; |
| return true; |
| } |
| return false; |
| }, |
| |
| reset: function() |
| { |
| delete this.minimumBoundary; |
| delete this.maximumBoundary; |
| }, |
| |
| _value: function(item) |
| { |
| return 0; |
| }, |
| |
| formatValue: function(value) |
| { |
| return value.toString(); |
| } |
| } |
| |
| WebInspector.NetworkTimeCalculator = function(startAtZero) |
| { |
| WebInspector.NetworkBaseCalculator.call(this); |
| this.startAtZero = startAtZero; |
| } |
| |
| WebInspector.NetworkTimeCalculator.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 (resource.timing) |
| return {left: leftLabel, right: 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); |
| }, |
| |
| _lowerBound: function(resource) |
| { |
| return 0; |
| }, |
| |
| _upperBound: function(resource) |
| { |
| return 0; |
| } |
| } |
| |
| WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype; |
| |
| WebInspector.NetworkTransferTimeCalculator = function() |
| { |
| WebInspector.NetworkTimeCalculator.call(this, false); |
| } |
| |
| WebInspector.NetworkTransferTimeCalculator.prototype = { |
| formatValue: function(value) |
| { |
| return Number.secondsToString(value); |
| }, |
| |
| _lowerBound: function(resource) |
| { |
| return resource.startTime; |
| }, |
| |
| _upperBound: function(resource) |
| { |
| return resource.endTime; |
| } |
| } |
| |
| WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype; |
| |
| WebInspector.NetworkTransferDurationCalculator = function() |
| { |
| WebInspector.NetworkTimeCalculator.call(this, true); |
| } |
| |
| WebInspector.NetworkTransferDurationCalculator.prototype = { |
| formatValue: function(value) |
| { |
| return Number.secondsToString(value); |
| }, |
| |
| _upperBound: function(resource) |
| { |
| return resource.duration; |
| } |
| } |
| |
| WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype; |
| |
| WebInspector.NetworkDataGridNode = function(panel, resource) |
| { |
| WebInspector.DataGridNode.call(this, {}); |
| this._panel = panel; |
| this._resource = resource; |
| } |
| |
| WebInspector.NetworkDataGridNode.prototype = { |
| createCells: function() |
| { |
| this._nameCell = this._createDivInTD("name"); |
| this._methodCell = this._createDivInTD("method"); |
| this._statusCell = this._createDivInTD("status"); |
| this._typeCell = this._createDivInTD("type"); |
| this._sizeCell = this._createDivInTD("size"); |
| this._timeCell = this._createDivInTD("time"); |
| this._createTimelineCell(); |
| this._nameCell.addEventListener("click", this.select.bind(this), false); |
| this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false); |
| }, |
| |
| isFilteredOut: function() |
| { |
| if (!this._panel._hiddenCategories.all) |
| return false; |
| return this._resource.category.name in this._panel._hiddenCategories; |
| }, |
| |
| select: function() |
| { |
| this._panel._showResource(this._resource); |
| WebInspector.DataGridNode.prototype.select.apply(this, arguments); |
| }, |
| |
| _openInNewTab: function() |
| { |
| InspectorBackend.openInInspectedWindow(this._resource.url); |
| }, |
| |
| get selectable() |
| { |
| if (!this._panel._viewingResourceMode) |
| return false; |
| return !this.isFilteredOut(); |
| }, |
| |
| _createDivInTD: function(columnIdentifier) |
| { |
| var td = document.createElement("td"); |
| td.className = columnIdentifier + "-column"; |
| var div = document.createElement("div"); |
| td.appendChild(div); |
| this._element.appendChild(td); |
| return div; |
| }, |
| |
| _createTimelineCell: function() |
| { |
| this._graphElement = document.createElement("div"); |
| this._graphElement.className = "network-graph-side"; |
| |
| this._barAreaElement = document.createElement("div"); |
| // this._barAreaElement.className = "network-graph-bar-area hidden"; |
| this._barAreaElement.className = "network-graph-bar-area"; |
| this._barAreaElement.resource = this._resource; |
| this._graphElement.appendChild(this._barAreaElement); |
| |
| this._barLeftElement = document.createElement("div"); |
| this._barLeftElement.className = "network-graph-bar waiting"; |
| this._barAreaElement.appendChild(this._barLeftElement); |
| |
| this._barRightElement = document.createElement("div"); |
| this._barRightElement.className = "network-graph-bar"; |
| this._barAreaElement.appendChild(this._barRightElement); |
| |
| |
| this._labelLeftElement = document.createElement("div"); |
| this._labelLeftElement.className = "network-graph-label waiting"; |
| this._barAreaElement.appendChild(this._labelLeftElement); |
| |
| this._labelRightElement = document.createElement("div"); |
| this._labelRightElement.className = "network-graph-label"; |
| this._barAreaElement.appendChild(this._labelRightElement); |
| |
| this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false); |
| |
| this._timelineCell = document.createElement("td"); |
| this._timelineCell.className = "timeline-column"; |
| this._element.appendChild(this._timelineCell); |
| this._timelineCell.appendChild(this._graphElement); |
| }, |
| |
| refreshResource: function() |
| { |
| this._refreshNameCell(); |
| |
| this._methodCell.textContent = this._resource.requestMethod; |
| |
| this._refreshStatusCell(); |
| |
| if (this._resource.mimeType) { |
| this._typeCell.removeStyleClass("network-dim-cell"); |
| this._typeCell.textContent = this._resource.mimeType; |
| } else { |
| this._typeCell.addStyleClass("network-dim-cell"); |
| this._typeCell.textContent = WebInspector.UIString("Pending"); |
| } |
| |
| this._refreshSizeCell(); |
| this._refreshTimeCell(); |
| |
| if (this._resource.cached) |
| this._graphElement.addStyleClass("resource-cached"); |
| |
| this._element.addStyleClass("network-item"); |
| if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) { |
| this._element.removeMatchingStyleClasses("network-category-\\w+"); |
| this._element.addStyleClass("network-category-" + this._resource.category.name); |
| } |
| }, |
| |
| _refreshNameCell: function() |
| { |
| this._nameCell.removeChildren(); |
| |
| if (this._resource.category === WebInspector.resourceCategories.images) { |
| var previewImage = document.createElement("img"); |
| previewImage.className = "image-network-icon-preview"; |
| this._resource.populateImageSource(previewImage); |
| |
| var iconElement = document.createElement("div"); |
| iconElement.className = "icon"; |
| iconElement.appendChild(previewImage); |
| } else { |
| var iconElement = document.createElement("img"); |
| iconElement.className = "icon"; |
| } |
| this._nameCell.appendChild(iconElement); |
| this._nameCell.appendChild(document.createTextNode(this._fileName())); |
| |
| |
| 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); |
| } |
| |
| this._appendSubtitle(this._nameCell, subtitle); |
| this._nameCell.title = this._resource.url; |
| }, |
| |
| _fileName: function() |
| { |
| var fileName = this._resource.displayName; |
| if (this._resource.queryString) |
| fileName += "?" + this._resource.queryString; |
| return fileName; |
| }, |
| |
| _refreshStatusCell: function() |
| { |
| this._statusCell.removeChildren(); |
| |
| var fromCache = this._resource.cached; |
| if (fromCache) { |
| this._statusCell.textContent = WebInspector.UIString("(from cache)"); |
| this._statusCell.addStyleClass("network-dim-cell"); |
| return; |
| } |
| |
| this._statusCell.removeStyleClass("network-dim-cell"); |
| if (this._resource.statusCode) { |
| this._statusCell.appendChild(document.createTextNode(this._resource.statusCode)); |
| this._statusCell.removeStyleClass("network-dim-cell"); |
| this._appendSubtitle(this._statusCell, this._resource.statusText); |
| this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText; |
| } else { |
| this._statusCell.addStyleClass("network-dim-cell"); |
| this._statusCell.textContent = WebInspector.UIString("Pending"); |
| } |
| }, |
| |
| _refreshSizeCell: function() |
| { |
| var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?"; |
| var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?"; |
| var fromCache = this._resource.cached; |
| this._sizeCell.textContent = !fromCache ? resourceSize : WebInspector.UIString("(from cache)"); |
| if (fromCache) |
| this._sizeCell.addStyleClass("network-dim-cell"); |
| else |
| this._sizeCell.removeStyleClass("network-dim-cell"); |
| if (!fromCache) |
| this._appendSubtitle(this._sizeCell, transferSize); |
| }, |
| |
| _refreshTimeCell: function() |
| { |
| if (this._resource.duration > 0) { |
| this._timeCell.removeStyleClass("network-dim-cell"); |
| this._timeCell.textContent = Number.secondsToString(this._resource.duration); |
| this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency)); |
| } else { |
| this._timeCell.addStyleClass("network-dim-cell"); |
| this._timeCell.textContent = WebInspector.UIString("Pending"); |
| } |
| }, |
| |
| _appendSubtitle: function(cellElement, subtitleText) |
| { |
| var subtitleElement = document.createElement("div"); |
| subtitleElement.className = "network-cell-subtitle"; |
| subtitleElement.textContent = subtitleText; |
| cellElement.appendChild(subtitleElement); |
| }, |
| |
| refreshGraph: function(calculator) |
| { |
| var percentages = calculator.computeBarGraphPercentages(this._resource); |
| this._percentages = percentages; |
| |
| this._barAreaElement.removeStyleClass("hidden"); |
| |
| if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) { |
| this._graphElement.removeMatchingStyleClasses("network-category-\\w+"); |
| this._graphElement.addStyleClass("network-category-" + this._resource.category.name); |
| } |
| |
| this._barLeftElement.style.setProperty("left", percentages.start + "%"); |
| this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); |
| |
| this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); |
| this._barRightElement.style.setProperty("left", percentages.middle + "%"); |
| |
| var labels = calculator.computeBarGraphLabels(this._resource); |
| 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; |
| }, |
| |
| _refreshLabelPositions: function() |
| { |
| if (!this._percentages) |
| return; |
| 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._barLeftElement) { |
| 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) + "%"); |
| } |
| } |
| } |
| |
| WebInspector.NetworkDataGridNode.NameComparator = function(a, b) |
| { |
| var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : ""); |
| var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : ""); |
| if (aFileName > bFileName) |
| return 1; |
| if (bFileName > aFileName) |
| return -1; |
| return 0; |
| } |
| |
| WebInspector.NetworkDataGridNode.SizeComparator = function(a, b) |
| { |
| if (b._resource.cached && !a._resource.cached) |
| return 1; |
| if (a._resource.cached && !b._resource.cached) |
| return -1; |
| |
| if (a._resource.resourceSize === b._resource.resourceSize) |
| return 0; |
| |
| return a._resource.resourceSize - b._resource.resourceSize; |
| } |
| |
| WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b) |
| { |
| var aValue = a._resource[propertyName]; |
| var bValue = b._resource[propertyName]; |
| if (aValue > bValue) |
| return revert ? -1 : 1; |
| if (bValue > aValue) |
| return revert ? 1 : -1; |
| return 0; |
| } |
| |
| WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; |
| |
| WebInspector.NetworkTotalGridNode = function(element) |
| { |
| this._summaryBarElement = element; |
| WebInspector.DataGridNode.call(this, {summaryRow: true}); |
| } |
| |
| WebInspector.NetworkTotalGridNode.prototype = { |
| isFilteredOut: function() |
| { |
| return false; |
| }, |
| |
| get selectable() |
| { |
| return false; |
| }, |
| |
| createCells: function() |
| { |
| var td = document.createElement("td"); |
| td.setAttribute("colspan", 7); |
| td.className = "network-summary"; |
| td.appendChild(this._summaryBarElement); |
| this._element.appendChild(td); |
| } |
| } |
| |
| WebInspector.NetworkTotalGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; |