node-inspector-sans-ws
Version:
Web Inspector based nodeJS debugger
1,278 lines (1,086 loc) • 93.1 kB
JavaScript
/*
* 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.
*/
importScript("RequestView.js");
importScript("NetworkItemView.js");
importScript("RequestCookiesView.js");
importScript("RequestHeadersView.js");
importScript("RequestHTMLView.js");
importScript("RequestJSONView.js");
importScript("RequestPreviewView.js");
importScript("RequestResponseView.js");
importScript("RequestTimingView.js");
importScript("ResourceWebSocketFrameView.js");
/**
* @constructor
* @extends {WebInspector.View}
* @param {WebInspector.Setting} coulmnsVisibilitySetting
*/
WebInspector.NetworkLogView = function(coulmnsVisibilitySetting)
{
WebInspector.View.call(this);
this.registerRequiredCSS("networkLogView.css");
this._coulmnsVisibilitySetting = coulmnsVisibilitySetting;
this._allowRequestSelection = false;
this._requests = [];
this._requestsById = {};
this._requestsByURL = {};
this._staleRequests = {};
this._requestGridNodes = {};
this._lastRequestGridNodeId = 0;
this._mainRequestLoadTime = -1;
this._mainRequestDOMContentLoadedTime = -1;
this._typeFilterElements = {};
this._typeFilter = WebInspector.NetworkLogView._trivialTypeFilter;
this._matchedRequests = [];
this._highlightedSubstringChanges = [];
this._filteredOutRequests = new Map();
this._matchedRequestsMap = {};
this._currentMatchedRequestIndex = -1;
this._createStatusbarButtons();
this._createStatusBarItems();
this._linkifier = new WebInspector.Linkifier();
WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this);
WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestUpdated, this._onRequestUpdated, this);
WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestUpdated, this);
WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this);
WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.Load, this._loadEventFired, this);
WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this);
this._initializeView();
WebInspector.networkLog.requests.forEach(this._appendRequest.bind(this));
}
WebInspector.NetworkLogView.HTTPSchemas = {"http": true, "https": true, "ws": true, "wss": true};
WebInspector.NetworkLogView._responseHeaderColumns = ["Cache-Control", "Connection", "Content-Encoding", "Content-Length", "ETag", "Keep-Alive", "Last-Modified", "Server", "Vary"];
WebInspector.NetworkLogView._defaultColumnsVisibility = {
method: true, status: true, scheme: false, domain: false, type: true, initiator: true, cookies: false, setCookies: false, size: true, time: true,
"Cache-Control": false, "Connection": false, "Content-Encoding": false, "Content-Length": false, "ETag": false, "Keep-Alive": false, "Last-Modified": false, "Server": false, "Vary": false
};
WebInspector.NetworkLogView._defaultRefreshDelay = 500;
WebInspector.NetworkLogView.ALL_TYPES = "all";
WebInspector.NetworkLogView.prototype = {
_initializeView: function()
{
this.element.id = "network-container";
this._createSortingFunctions();
this._createTable();
this._createTimelineGrid();
this._createSummaryBar();
if (!this.useLargeRows)
this._setLargerRequests(this.useLargeRows);
this._allowPopover = true;
this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), this._onHidePopover.bind(this));
// Enable faster hint.
this._popoverHelper.setTimeout(100);
this.calculator = new WebInspector.NetworkTransferTimeCalculator();
this._toggleTypeFilter(WebInspector.NetworkLogView.ALL_TYPES, false);
this.switchToDetailedView();
},
get statusBarItems()
{
return [this._largerRequestsButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement, this._progressBarContainer];
},
get useLargeRows()
{
return WebInspector.settings.resourcesLargeRows.get();
},
set allowPopover(flag)
{
this._allowPopover = flag;
},
elementsToRestoreScrollPositionsFor: function()
{
if (!this._dataGrid) // Not initialized yet.
return [];
return [this._dataGrid.scrollContainer];
},
onResize: function()
{
this._updateOffscreenRows();
},
_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 = [];
columns.push({
id: "name",
titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path")),
title: WebInspector.UIString("Name"),
sortable: true,
weight: 20,
disclosure: true
});
columns.push({
id: "method",
title: WebInspector.UIString("Method"),
sortable: true,
weight: 6
});
columns.push({
id: "status",
titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text")),
title: WebInspector.UIString("Status"),
sortable: true,
weight: 6
});
columns.push({
id: "scheme",
title: WebInspector.UIString("Scheme"),
sortable: true,
weight: 6
});
columns.push({
id: "domain",
title: WebInspector.UIString("Domain"),
sortable: true,
weight: 6
});
columns.push({
id: "type",
title: WebInspector.UIString("Type"),
sortable: true,
weight: 6
});
columns.push({
id: "initiator",
title: WebInspector.UIString("Initiator"),
sortable: true,
weight: 10
});
columns.push({
id: "cookies",
title: WebInspector.UIString("Cookies"),
sortable: true,
weight: 6,
align: WebInspector.DataGrid.Align.Right
});
columns.push({
id: "setCookies",
title: WebInspector.UIString("Set-Cookies"),
sortable: true,
weight: 6,
align: WebInspector.DataGrid.Align.Right
});
columns.push({
id: "size",
titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Content")),
title: WebInspector.UIString("Size"),
sortable: true,
weight: 6,
align: WebInspector.DataGrid.Align.Right
});
columns.push({
id: "time",
titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency")),
title: WebInspector.UIString("Time"),
sortable: true,
weight: 6,
align: WebInspector.DataGrid.Align.Right
});
var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns;
for (var i = 0; i < responseHeaderColumns.length; ++i) {
var headerName = responseHeaderColumns[i];
var descriptor = {
id: headerName,
title: WebInspector.UIString(headerName),
weight: 6
}
if (headerName === "Content-Length")
descriptor.align = WebInspector.DataGrid.Align.Right;
columns.push(descriptor);
}
columns.push({
id: "timeline",
titleDOMFragment: document.createDocumentFragment(),
title: WebInspector.UIString("Timeline"),
sortable: false,
weight: 40,
sort: WebInspector.DataGrid.Order.Ascending
});
this._dataGrid = new WebInspector.DataGrid(columns);
this._dataGrid.setName("networkLog");
this._dataGrid.resizeMethod = WebInspector.DataGrid.ResizeMethod.Last;
this._dataGrid.element.addStyleClass("network-log-grid");
this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
this._dataGrid.show(this.element);
// Event listeners need to be added _after_ we attach to the document, so that owner document is properly update.
this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortItems, this);
this._dataGrid.addEventListener(WebInspector.DataGrid.Events.ColumnsResized, this._updateDividersIfNeeded, this);
this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this));
this._patchTimelineHeader();
},
_makeHeaderFragment: function(title, subtitle)
{
var fragment = document.createDocumentFragment();
fragment.createTextChild(title);
var subtitleDiv = fragment.createChild("div", "network-header-subtitle");
subtitleDiv.createTextChild(subtitle);
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.consume() }, 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.RequestPropertyComparator.bind(null, "method", false);
this._sortingFunctions.status = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "statusCode", false);
this._sortingFunctions.scheme = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "scheme", false);
this._sortingFunctions.domain = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "domain", false);
this._sortingFunctions.type = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "mimeType", false);
this._sortingFunctions.initiator = WebInspector.NetworkDataGridNode.InitiatorComparator;
this._sortingFunctions.cookies = WebInspector.NetworkDataGridNode.RequestCookiesCountComparator;
this._sortingFunctions.setCookies = WebInspector.NetworkDataGridNode.ResponseCookiesCountComparator;
this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator;
this._sortingFunctions.time = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", false);
this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false);
this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false);
this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "endTime", false);
this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "responseReceivedTime", false);
this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", true);
this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.RequestPropertyComparator.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()
{
this._removeAllNodeHighlights();
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.isSortOrderAscending());
this._timelineSortSelector.selectedIndex = 0;
this._updateOffscreenRows();
this.searchCanceled();
WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
action: WebInspector.UserMetrics.UserActionNames.NetworkSort,
column: columnIdentifier,
sortOrder: this._dataGrid.sortOrder()
});
},
_sortByTimeline: function()
{
this._removeAllNodeHighlights();
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", WebInspector.DataGrid.Order.Ascending);
this._updateOffscreenRows();
},
/**
* @param {string} typeName
* @param {string} label
*/
_addTypeFilter: function(typeName, label)
{
var typeFilterElement = this._filterBarElement.createChild("li", typeName);
typeFilterElement.typeName = typeName;
typeFilterElement.createTextChild(label);
typeFilterElement.addEventListener("click", this._onTypeFilterClicked.bind(this), false);
this._typeFilterElements[typeName] = typeFilterElement;
},
_createStatusBarItems: function()
{
var filterBarElement = document.createElement("div");
filterBarElement.className = "scope-bar status-bar-item";
filterBarElement.title = WebInspector.UIString("Use %s Click to select multiple types.", WebInspector.KeyboardShortcut.shortcutToString("", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta));
this._filterBarElement = filterBarElement;
this._addTypeFilter(WebInspector.NetworkLogView.ALL_TYPES, WebInspector.UIString("All"));
filterBarElement.createChild("div", "scope-bar-divider");
for (var typeId in WebInspector.resourceTypes) {
var type = WebInspector.resourceTypes[typeId];
this._addTypeFilter(type.name(), type.categoryTitle());
}
this._progressBarContainer = document.createElement("div");
this._progressBarContainer.className = "status-bar-item";
},
_createSummaryBar: function()
{
var tbody = this._dataGrid.dataTableBody;
var tfoot = document.createElement("tfoot");
var tr = tfoot.createChild("tr", "revealed network-summary-bar");
var td = tr.createChild("td");
td.setAttribute("colspan", 7);
tbody.parentNode.insertBefore(tfoot, tbody);
this._summaryBarElement = td;
},
_updateSummaryBar: function()
{
var requestsNumber = this._requests.length;
if (!requestsNumber) {
if (this._summaryBarElement._isDisplayingWarning)
return;
this._summaryBarElement._isDisplayingWarning = true;
this._summaryBarElement.removeChildren();
this._summaryBarElement.createChild("div", "warning-icon-small");
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 selectedRequestsNumber = 0;
var selectedTransferSize = 0;
var baseTime = -1;
var maxTime = -1;
for (var i = 0; i < this._requests.length; ++i) {
var request = this._requests[i];
var requestTransferSize = request.transferSize;
transferSize += requestTransferSize;
if (!this._filteredOutRequests.get(request)) {
selectedRequestsNumber++;
selectedTransferSize += requestTransferSize;
}
if (request.url === WebInspector.inspectedPageURL)
baseTime = request.startTime;
if (request.endTime > maxTime)
maxTime = request.endTime;
}
var text = "";
if (selectedRequestsNumber !== requestsNumber) {
text += String.sprintf(WebInspector.UIString("%d / %d requests"), selectedRequestsNumber, requestsNumber);
text += " \u2758 " + String.sprintf(WebInspector.UIString("%s / %s transferred"), Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize));
} else {
text += String.sprintf(WebInspector.UIString("%d requests"), requestsNumber);
text += " \u2758 " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize));
}
if (baseTime !== -1 && this._mainRequestLoadTime !== -1 && this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) {
text += " \u2758 " + String.sprintf(WebInspector.UIString("%s (load: %s, DOMContentLoaded: %s)"),
Number.secondsToString(maxTime - baseTime),
Number.secondsToString(this._mainRequestLoadTime - baseTime),
Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime));
}
this._summaryBarElement.textContent = text;
},
/**
* @param {!Event} e
*/
_onTypeFilterClicked: function(e)
{
var toggle;
if (WebInspector.isMac())
toggle = e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey;
else
toggle = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey;
this._toggleTypeFilter(e.target.typeName, toggle);
this._removeAllNodeHighlights();
this.searchCanceled();
this._filterRequests();
},
/**
* @param {string} typeName
* @param {boolean} allowMultiSelect
*/
_toggleTypeFilter: function(typeName, allowMultiSelect)
{
if (allowMultiSelect && typeName !== WebInspector.NetworkLogView.ALL_TYPES)
this._typeFilterElements[WebInspector.NetworkLogView.ALL_TYPES].removeStyleClass("selected");
else {
for (var key in this._typeFilterElements)
this._typeFilterElements[key].removeStyleClass("selected");
}
var filterElement = this._typeFilterElements[typeName];
filterElement.enableStyleClass("selected", !filterElement.hasStyleClass("selected"));
var allowedTypes = {};
for (var key in this._typeFilterElements) {
if (this._typeFilterElements[key].hasStyleClass("selected"))
allowedTypes[key] = true;
}
if (typeName === WebInspector.NetworkLogView.ALL_TYPES)
this._typeFilter = WebInspector.NetworkLogView._trivialTypeFilter;
else
this._typeFilter = WebInspector.NetworkLogView._typeFilter.bind(null, allowedTypes);
},
_scheduleRefresh: function()
{
if (this._needsRefresh)
return;
this._needsRefresh = true;
if (this.isShowing() && !this._refreshTimeout)
this._refreshTimeout = setTimeout(this.refresh.bind(this), WebInspector.NetworkLogView._defaultRefreshDelay);
},
_updateDividersIfNeeded: function()
{
if (!this._dataGrid)
return;
var timelineColumn = this._dataGrid.columns.timeline;
for (var i = 0; i < this._dataGrid.resizers.length; ++i) {
if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnIndex) {
// Position timline grid location.
this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left;
}
}
var proceed = true;
if (!this.isShowing()) {
this._scheduleRefresh();
proceed = false;
} else {
this.calculator.setDisplayWindow(this._timelineGrid.dividersElement.clientWidth);
proceed = this._timelineGrid.updateDividers(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
// requests 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._mainRequestLoadTime !== -1) {
var percent = this.calculator.computePercentageFromEventTime(this._mainRequestLoadTime);
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._mainRequestDOMContentLoadedTime !== -1) {
var percent = this.calculator.computePercentageFromEventTime(this._mainRequestDOMContentLoadedTime);
var domContentLoadedDivider = document.createElement("div");
domContentLoadedDivider.className = "network-event-divider network-blue-divider";
var domContentLoadedDividerPadding = document.createElement("div");
domContentLoadedDividerPadding.className = "network-event-divider-padding";
domContentLoadedDividerPadding.title = WebInspector.UIString("DOMContentLoaded event fired");
domContentLoadedDividerPadding.appendChild(domContentLoadedDivider);
domContentLoadedDividerPadding.style.left = percent + "%";
this._timelineGrid.addEventDivider(domContentLoadedDividerPadding);
}
},
_refreshIfNeeded: function()
{
if (this._needsRefresh)
this.refresh();
},
_invalidateAllItems: function()
{
for (var i = 0; i < this._requests.length; ++i) {
var request = this._requests[i];
this._staleRequests[request.requestId] = request;
}
},
get calculator()
{
return this._calculator;
},
set calculator(x)
{
if (!x || this._calculator === x)
return;
this._calculator = x;
this._calculator.reset();
this._invalidateAllItems();
this.refresh();
},
_requestGridNode: function(request)
{
return this._requestGridNodes[request.__gridNodeId];
},
_createRequestGridNode: function(request)
{
var node = new WebInspector.NetworkDataGridNode(this, request);
request.__gridNodeId = this._lastRequestGridNodeId++;
this._requestGridNodes[request.__gridNodeId] = node;
return node;
},
_createStatusbarButtons: function()
{
this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item");
this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked, this);
this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
this._clearButton.addEventListener("click", this._reset, this);
this._largerRequestsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
this._largerRequestsButton.toggled = WebInspector.settings.resourcesLargeRows.get();
this._largerRequestsButton.addEventListener("click", this._toggleLargerRequests, this);
},
_loadEventFired: function(event)
{
this._mainRequestLoadTime = event.data || -1;
// Schedule refresh to update boundaries and draw the new line.
this._scheduleRefresh();
},
_domContentLoadedEventFired: function(event)
{
this._mainRequestDOMContentLoadedTime = event.data || -1;
// Schedule refresh to update boundaries and draw the new line.
this._scheduleRefresh();
},
wasShown: function()
{
this._refreshIfNeeded();
},
willHide: function()
{
this._popoverHelper.hidePopover();
},
refresh: function()
{
this._needsRefresh = false;
if (this._refreshTimeout) {
clearTimeout(this._refreshTimeout);
delete this._refreshTimeout;
}
this._removeAllNodeHighlights();
var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow();
var boundariesChanged = false;
if (this.calculator.updateBoundariesForEventTime) {
boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainRequestLoadTime) || boundariesChanged;
boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime) || boundariesChanged;
}
for (var requestId in this._staleRequests) {
var request = this._staleRequests[requestId];
var node = this._requestGridNode(request);
if (!node) {
// Create the timeline tree element and graph.
node = this._createRequestGridNode(request);
this._dataGrid.rootNode().appendChild(node);
}
node.refreshRequest();
this._applyFilter(node);
if (this.calculator.updateBoundaries(request))
boundariesChanged = true;
if (!node.isFilteredOut())
this._updateHighlightIfMatched(request);
}
if (boundariesChanged) {
// The boundaries changed, so all item graphs are stale.
this._invalidateAllItems();
}
for (var requestId in this._staleRequests)
this._requestGridNode(this._staleRequests[requestId]).refreshGraph(this.calculator);
this._staleRequests = {};
this._sortItems();
this._updateSummaryBar();
this._dataGrid.updateWidths();
// FIXME: evaluate performance impact of moving this before a call to sortItems()
if (wasScrolledToLastRow)
this._dataGrid.scrollToLastRow();
},
_onPreserveLogClicked: function(e)
{
this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled;
},
_reset: function()
{
this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared);
this._clearSearchMatchedList();
if (this._popoverHelper)
this._popoverHelper.hidePopover();
if (this._calculator)
this._calculator.reset();
this._requests = [];
this._requestsById = {};
this._requestsByURL = {};
this._staleRequests = {};
this._requestGridNodes = {};
if (this._dataGrid) {
this._dataGrid.rootNode().removeChildren();
this._updateDividersIfNeeded();
this._updateSummaryBar();
}
this._mainRequestLoadTime = -1;
this._mainRequestDOMContentLoadedTime = -1;
},
get requests()
{
return this._requests;
},
requestById: function(id)
{
return this._requestsById[id];
},
_onRequestStarted: function(event)
{
this._appendRequest(event.data);
},
_appendRequest: function(request)
{
this._requests.push(request);
// In case of redirect request id is reassigned to a redirected
// request and we need to update _requestsById and search results.
if (this._requestsById[request.requestId]) {
var oldRequest = request.redirects[request.redirects.length - 1];
this._requestsById[oldRequest.requestId] = oldRequest;
this._updateSearchMatchedListAfterRequestIdChanged(request.requestId, oldRequest.requestId);
}
this._requestsById[request.requestId] = request;
this._requestsByURL[request.url] = request;
// Pull all the redirects of the main request upon commit load.
if (request.redirects) {
for (var i = 0; i < request.redirects.length; ++i)
this._refreshRequest(request.redirects[i]);
}
this._refreshRequest(request);
},
/**
* @param {WebInspector.Event} event
*/
_onRequestUpdated: function(event)
{
var request = /** @type {WebInspector.NetworkRequest} */ (event.data);
this._refreshRequest(request);
},
/**
* @param {WebInspector.NetworkRequest} request
*/
_refreshRequest: function(request)
{
this._staleRequests[request.requestId] = request;
this._scheduleRefresh();
},
clear: function()
{
if (this._preserveLogToggle.toggled)
return;
this._reset();
},
_mainFrameNavigated: function(event)
{
if (this._preserveLogToggle.toggled)
return;
var frame = /** @type {WebInspector.ResourceTreeFrame} */ (event.data);
var loaderId = frame.loaderId;
// Preserve provisional load requests.
var requestsToPreserve = [];
for (var i = 0; i < this._requests.length; ++i) {
var request = this._requests[i];
if (request.loaderId === loaderId)
requestsToPreserve.push(request);
}
this._reset();
// Restore preserved items.
for (var i = 0; i < requestsToPreserve.length; ++i)
this._appendRequest(requestsToPreserve[i]);
},
switchToDetailedView: function()
{
if (!this._dataGrid)
return;
if (this._dataGrid.selectedNode)
this._dataGrid.selectedNode.selected = false;
this.element.removeStyleClass("brief-mode");
this._detailedMode = true;
this._updateColumns();
},
switchToBriefView: function()
{
this.element.addStyleClass("brief-mode");
this._removeAllNodeHighlights();
this._detailedMode = false;
this._updateColumns();
this._popoverHelper.hidePopover();
},
_toggleLargerRequests: function()
{
WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get());
this._setLargerRequests(WebInspector.settings.resourcesLargeRows.get());
},
_setLargerRequests: function(enabled)
{
this._largerRequestsButton.toggled = enabled;
if (!enabled) {
this._largerRequestsButton.title = WebInspector.UIString("Use large resource rows.");
this._dataGrid.element.addStyleClass("small");
this._timelineGrid.element.addStyleClass("small");
} else {
this._largerRequestsButton.title = WebInspector.UIString("Use small resource rows.");
this._dataGrid.element.removeStyleClass("small");
this._timelineGrid.element.removeStyleClass("small");
}
this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: enabled });
this._updateOffscreenRows();
},
_getPopoverAnchor: function(element)
{
if (!this._allowPopover)
return;
var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
if (anchor && anchor.parentElement.request && anchor.parentElement.request.timing)
return anchor;
anchor = element.enclosingNodeOrSelfWithClass("network-script-initiated");
if (anchor && anchor.request && anchor.request.initiator)
return anchor;
return null;
},
/**
* @param {Element} anchor
* @param {WebInspector.Popover} popover
*/
_showPopover: function(anchor, popover)
{
var content;
if (anchor.hasStyleClass("network-script-initiated"))
content = this._generateScriptInitiatedPopoverContent(anchor.request);
else
content = WebInspector.RequestTimingView.createTimingTable(anchor.parentElement.request);
popover.show(content, anchor);
},
_onHidePopover: function()
{
this._linkifier.reset();
},
/**
* @param {!WebInspector.NetworkRequest} request
* @return {!Element}
*/
_generateScriptInitiatedPopoverContent: function(request)
{
var stackTrace = request.initiator.stackTrace;
var framesTable = document.createElement("table");
for (var i = 0; i < stackTrace.length; ++i) {
var stackFrame = stackTrace[i];
var row = document.createElement("tr");
row.createChild("td").textContent = stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)");
row.createChild("td").textContent = " @ ";
row.createChild("td").appendChild(this._linkifier.linkifyLocation(stackFrame.url, stackFrame.lineNumber - 1, stackFrame.columnNumber - 1));
framesTable.appendChild(row);
}
return framesTable;
},
_updateColumns: function()
{
var columnsVisibility = this._coulmnsVisibilitySetting.get();
var detailedMode = !!this._detailedMode;
for (var columnIdentifier in columnsVisibility) {
var visible = detailedMode && columnsVisibility[columnIdentifier];
this._dataGrid.setColumnVisible(columnIdentifier, visible);
}
this._dataGrid.setColumnVisible("timeline", detailedMode);
this._dataGrid.applyColumnWeights();
},
/**
* @param {string} columnIdentifier
*/
_toggleColumnVisibility: function(columnIdentifier)
{
var columnsVisibility = this._coulmnsVisibilitySetting.get();
columnsVisibility[columnIdentifier] = !columnsVisibility[columnIdentifier];
this._coulmnsVisibilitySetting.set(columnsVisibility);
this._updateColumns();
},
/**
* @return {!Array.<string>}
*/
_getConfigurableColumnIDs: function()
{
if (this._configurableColumnIDs)
return this._configurableColumnIDs;
var columns = this._dataGrid.columns;
function compare(id1, id2)
{
return columns[id1].title.compareTo(columns[id2].title);
}
var columnIDs = Object.keys(this._coulmnsVisibilitySetting.get());
this._configurableColumnIDs = columnIDs.sort(compare);
return this._configurableColumnIDs;
},
_contextMenu: function(event)
{
var contextMenu = new WebInspector.ContextMenu(event);
if (this._detailedMode && event.target.isSelfOrDescendant(this._dataGrid.headerTableBody)) {
var columnsVisibility = this._coulmnsVisibilitySetting.get();
var columnIDs = this._getConfigurableColumnIDs();
for (var i = 0; i < columnIDs.length; ++i) {
var columnIdentifier = columnIDs[i];
var column = this._dataGrid.columns[columnIdentifier];
contextMenu.appendCheckboxItem(column.title, this._toggleColumnVisibility.bind(this, columnIdentifier), !!columnsVisibility[columnIdentifier]);
}
contextMenu.show();
return;
}
var gridNode = this._dataGrid.dataGridNodeFromNode(event.target);
var request = gridNode && gridNode._request;
if (request) {
contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, request.url, false));
contextMenu.appendSeparator();
contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, request));
if (request.requestHeadersText)
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, request));
if (request.responseHeadersText)
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, request));
contextMenu.appendItem(WebInspector.UIString("Copy as cURL"), this._copyCurlCommand.bind(this, request));
}
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this));
contextMenu.appendSeparator();
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as HAR with content" : "Save as HAR with Content"), this._exportAll.bind(this));
contextMenu.appendSeparator();
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this));
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this));
if (request && request.type === WebInspector.resourceTypes.XHR) {
contextMenu.appendSeparator();
contextMenu.appendItem(WebInspector.UIString("Replay XHR"), this._replayXHR.bind(this, request.requestId));
contextMenu.appendSeparator();
}
contextMenu.show();
},
_replayXHR: function(requestId)
{
NetworkAgent.replayXHR(requestId);
},
_copyAll: function()
{
var harArchive = {
log: (new WebInspector.HARLog(this._requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter))).build()
};
InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2));
},
_copyLocation: function(request)
{
InspectorFrontendHost.copyText(request.url);
},
_copyRequestHeaders: function(request)
{
InspectorFrontendHost.copyText(request.requestHeadersText);
},
_copyResponseHeaders: function(request)
{
InspectorFrontendHost.copyText(request.responseHeadersText);
},
/**
* @param {WebInspector.NetworkRequest} request
*/
_copyCurlCommand: function(request)
{
InspectorFrontendHost.copyText(this._generateCurlCommand(request));
},
_exportAll: function()
{
var filename = WebInspector.inspectedPageDomain + ".har";
var stream = new WebInspector.FileOutputStream();
stream.open(filename, openCallback.bind(this));
function openCallback()
{
var progressIndicator = new WebInspector.ProgressIndicator();
this._progressBarContainer.appendChild(progressIndicator.element);
var harWriter = new WebInspector.HARWriter();
harWriter.write(stream, this._requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter), progressIndicator);
}
},
_clearBrowserCache: function()
{
if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?")))
NetworkAgent.clearBrowserCache();
},
_clearBrowserCookies: function()
{
if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?")))
NetworkAgent.clearBrowserCookies();
},
_updateOffscreenRows: function()
{
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];
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) {
row.enableStyleClass("offscreen", !rowIsVisible);
row.rowIsVisible = rowIsVisible;
}
unfilteredRowIndex++;
}
},
_matchRequest: function(request)
{
if (!this._searchRegExp)
return -1;
if (!request.name().match(this._searchRegExp) && !request.path().match(this._searchRegExp))
return -1;
if (request.requestId in this._matchedRequestsMap)
return this._matchedRequestsMap[request.requestId];
var matchedRequestIndex = this._matchedRequests.length;
this._matchedRequestsMap[request.requestId] = matchedRequestIndex;
this._matchedRequests.push(request.requestId);
return matchedRequestIndex;
},
_clearSearchMatchedList: function()
{
delete this._searchRegExp;
this._matchedRequests = [];
this._matchedRequestsMap = {};
this._removeAllHighlights();
},
_updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId)
{
var requestIndex = this._matchedRequestsMap[oldRequestId];
if (requestIndex) {
delete this._matchedRequestsMap[oldRequestId];
this._matchedRequestsMap[newRequestId] = requestIndex;
this._matchedRequests[requestIndex] = newRequestId;
}
},
_updateHighlightIfMatched: function(request)
{
var matchedRequestIndex = this._matchRequest(request);
if (matchedRequestIndex === -1)
return;
this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedRequests.length);
if (this._currentMatchedRequestIndex !== -1 && this._currentMatchedRequestIndex !== matchedRequestIndex)
return;
this._highlightNthMatchedRequestForSearch(matchedRequestIndex, false);
},
_removeAllHighlights: function()
{
this._removeAllNodeHighlights();
for (var i = 0; i < this._highlightedSubstringChanges.length; ++i)
WebInspector.revertDomChanges(this._highlightedSubstringChanges[i]);
this._highlightedSubstringChanges = [];
},
/**
* @param {WebInspector.NetworkRequest} request
* @param {boolean} reveal
* @param {RegExp=} regExp
*/
_highlightMatchedRequest: function(request, reveal, regExp)
{
var node = this._requestGridNode(request);
if (!node)
return;
var nameMatched = request.name().match(regExp);
var pathMatched = request.path().match(regExp);
if (!nameMatched && pathMatched && !this._largerRequestsButton.toggled)
this._toggleLargerRequests();
var highlightedSubstringChanges = node._highlightMatchedSubstring(regExp);
this._highlightedSubstringChanges.push(highlightedSubstringChanges);
if (reveal) {
node.reveal();
this._highlightNode(node);
}
},
/**
* @param {number} matchedRequestIndex
* @param {boolean} reveal
*/
_highlightNthMatchedRequestForSearch: function(matchedRequestIndex, reveal)
{
var request = this.requestById(this._matchedRequests[matchedRequestIndex]);
if (!request)
return;
this._removeAllHighlights();
this._highlightMatchedRequest(request, reveal, this._searchRegExp);
var node = this._requestGridNode(request);
if (node)
this._currentMatchedRequestIndex = matchedRequestIndex;
this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedRequestIndex);
},
/**
* @param {string} query
* @param {boolean} shouldJump
*/
performSearch: function(query, shouldJump)
{
var newMatchedRequestIndex = 0;
var currentMatchedRequestId;
if (this._currentMatchedRequestIndex !== -1)
currentMatchedRequestId = this._matchedRequests[this._currentMatchedRequestIndex];
this._clearSearchMatchedList();
this._searchRegExp = createPlainTextSearchRegex(query, "i");
var childNodes = this._dataGrid.dataTableBody.childNodes;
var requestNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row.
for (var i = 0; i < requestNodes.length; ++i) {
var dataGridNode = this._dataGrid.dataGridNodeFromNode(requestNodes[i]);
if (dataGridNode.isFilteredOut())
contin