2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
4 * Copyright (C) 2011 Google Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @extends {WebInspector.View}
35 WebInspector.NetworkLogView = function()
37 WebInspector.View.call(this);
38 this.registerRequiredCSS("networkLogView.css");
40 this._allowResourceSelection = false;
42 this._resourcesById = {};
43 this._resourcesByURL = {};
44 this._staleResources = {};
45 this._resourceGridNodes = {};
46 this._lastResourceGridNodeId = 0;
47 this._mainResourceLoadTime = -1;
48 this._mainResourceDOMContentTime = -1;
49 this._hiddenCategories = {};
50 this._matchedResources = [];
51 this._matchedResourcesMap = {};
52 this._currentMatchedResourceIndex = -1;
54 this._categories = WebInspector.resourceCategories;
56 this._createStatusbarButtons();
57 this._createFilterStatusBarItems();
58 this._linkifier = WebInspector.debuggerPresentationModel.createLinkifier();
60 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this);
61 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._onResourceUpdated, this);
62 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceUpdated, this);
64 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this);
65 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._onLoadEventFired, this);
66 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this);
68 this._initializeView();
71 WebInspector.NetworkLogView.prototype = {
72 _initializeView: function()
74 this.element.id = "network-container";
76 this._createSortingFunctions();
78 this._createTimelineGrid();
79 this._createSummaryBar();
81 if (!this.useLargeRows)
82 this._setLargerResources(this.useLargeRows);
84 this._allowPopover = true;
85 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
86 // Enable faster hint.
87 this._popoverHelper.setTimeout(100);
89 this.calculator = new WebInspector.NetworkTransferTimeCalculator();
90 this._filter(this._filterAllElement, false);
92 this.switchToDetailedView();
97 return [this._largerResourcesButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement];
102 return WebInspector.settings.resourcesLargeRows.get();
105 set allowPopover(flag)
107 this._allowPopover = flag;
110 get allowResourceSelection()
112 return this._allowResourceSelection;
115 set allowResourceSelection(flag)
117 this._allowResourceSelection = !!flag;
120 elementsToRestoreScrollPositionsFor: function()
122 if (!this._dataGrid) // Not initialized yet.
124 return [this._dataGrid.scrollContainer];
129 this._updateOffscreenRows();
132 _createTimelineGrid: function()
134 this._timelineGrid = new WebInspector.TimelineGrid();
135 this._timelineGrid.element.addStyleClass("network-timeline-grid");
136 this._dataGrid.element.appendChild(this._timelineGrid.element);
139 _createTable: function()
142 if (Capabilities.nativeInstrumentationEnabled)
143 columns = {name: {}, method: {}, status: {}, type: {}, initiator: {}, size: {}, time: {}, timeline: {}};
145 columns = {name: {}, method: {}, status: {}, type: {}, size: {}, time: {}, timeline: {}};
146 columns.name.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path"));
147 columns.name.sortable = true;
148 columns.name.width = "20%";
149 columns.name.disclosure = true;
151 columns.method.title = WebInspector.UIString("Method");
152 columns.method.sortable = true;
153 columns.method.width = "6%";
155 columns.status.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text"));
156 columns.status.sortable = true;
157 columns.status.width = "6%";
159 columns.type.title = WebInspector.UIString("Type");
160 columns.type.sortable = true;
161 columns.type.width = "6%";
163 if (Capabilities.nativeInstrumentationEnabled) {
164 columns.initiator.title = WebInspector.UIString("Initiator");
165 columns.initiator.sortable = true;
166 columns.initiator.width = "10%";
169 columns.size.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Content"));
170 columns.size.sortable = true;
171 columns.size.width = "6%";
172 columns.size.aligned = "right";
174 columns.time.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency"));
175 columns.time.sortable = true;
176 columns.time.width = "6%";
177 columns.time.aligned = "right";
179 columns.timeline.title = "";
180 columns.timeline.sortable = false;
181 if (Capabilities.nativeInstrumentationEnabled)
182 columns.timeline.width = "40%";
184 columns.timeline.width = "50%";
185 columns.timeline.sort = "ascending";
187 this._dataGrid = new WebInspector.DataGrid(columns);
188 this._dataGrid.resizeMethod = WebInspector.DataGrid.ResizeMethod.Last;
189 this._dataGrid.element.addStyleClass("network-log-grid");
190 this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
191 this._dataGrid.show(this.element);
193 // Event listeners need to be added _after_ we attach to the document, so that owner document is properly update.
194 this._dataGrid.addEventListener("sorting changed", this._sortItems, this);
195 this._dataGrid.addEventListener("width changed", this._updateDividersIfNeeded, this);
196 this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this));
198 this._patchTimelineHeader();
201 _makeHeaderFragment: function(title, subtitle)
203 var fragment = document.createDocumentFragment();
204 fragment.appendChild(document.createTextNode(title));
205 var subtitleDiv = document.createElement("div");
206 subtitleDiv.className = "network-header-subtitle";
207 subtitleDiv.textContent = subtitle;
208 fragment.appendChild(subtitleDiv);
212 _patchTimelineHeader: function()
214 var timelineSorting = document.createElement("select");
216 var option = document.createElement("option");
217 option.value = "startTime";
218 option.label = WebInspector.UIString("Timeline");
219 timelineSorting.appendChild(option);
221 option = document.createElement("option");
222 option.value = "startTime";
223 option.label = WebInspector.UIString("Start Time");
224 timelineSorting.appendChild(option);
226 option = document.createElement("option");
227 option.value = "responseTime";
228 option.label = WebInspector.UIString("Response Time");
229 timelineSorting.appendChild(option);
231 option = document.createElement("option");
232 option.value = "endTime";
233 option.label = WebInspector.UIString("End Time");
234 timelineSorting.appendChild(option);
236 option = document.createElement("option");
237 option.value = "duration";
238 option.label = WebInspector.UIString("Duration");
239 timelineSorting.appendChild(option);
241 option = document.createElement("option");
242 option.value = "latency";
243 option.label = WebInspector.UIString("Latency");
244 timelineSorting.appendChild(option);
246 var header = this._dataGrid.headerTableHeader("timeline");
247 header.replaceChild(timelineSorting, header.firstChild);
249 timelineSorting.addEventListener("click", function(event) { event.stopPropagation() }, false);
250 timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false);
251 this._timelineSortSelector = timelineSorting;
254 _createSortingFunctions: function()
256 this._sortingFunctions = {};
257 this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator;
258 this._sortingFunctions.method = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "method", false);
259 this._sortingFunctions.status = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "statusCode", false);
260 this._sortingFunctions.type = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "mimeType", false);
261 this._sortingFunctions.initiator = WebInspector.NetworkDataGridNode.InitiatorComparator;
262 this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator;
263 this._sortingFunctions.time = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", false);
264 this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false);
265 this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false);
266 this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "endTime", false);
267 this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "responseReceivedTime", false);
268 this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", true);
269 this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "latency", true);
271 var timeCalculator = new WebInspector.NetworkTransferTimeCalculator();
272 var durationCalculator = new WebInspector.NetworkTransferDurationCalculator();
274 this._calculators = {};
275 this._calculators.timeline = timeCalculator;
276 this._calculators.startTime = timeCalculator;
277 this._calculators.endTime = timeCalculator;
278 this._calculators.responseTime = timeCalculator;
279 this._calculators.duration = durationCalculator;
280 this._calculators.latency = durationCalculator;
283 _sortItems: function()
285 this._removeAllNodeHighlights();
286 var columnIdentifier = this._dataGrid.sortColumnIdentifier;
287 if (columnIdentifier === "timeline") {
288 this._sortByTimeline();
291 var sortingFunction = this._sortingFunctions[columnIdentifier];
292 if (!sortingFunction)
295 this._dataGrid.sortNodes(sortingFunction, this._dataGrid.sortOrder === "descending");
296 this._timelineSortSelector.selectedIndex = 0;
297 this._updateOffscreenRows();
299 this.performSearch(null, true);
302 _sortByTimeline: function()
304 this._removeAllNodeHighlights();
305 var selectedIndex = this._timelineSortSelector.selectedIndex;
307 selectedIndex = 1; // Sort by start time by default.
308 var selectedOption = this._timelineSortSelector[selectedIndex];
309 var value = selectedOption.value;
311 var sortingFunction = this._sortingFunctions[value];
312 this._dataGrid.sortNodes(sortingFunction);
313 this.calculator = this._calculators[value];
314 if (this.calculator.startAtZero)
315 this._timelineGrid.hideEventDividers();
317 this._timelineGrid.showEventDividers();
318 this._dataGrid.markColumnAsSortedBy("timeline", "ascending");
319 this._updateOffscreenRows();
322 _createFilterStatusBarItems: function()
324 var filterBarElement = document.createElement("div");
325 filterBarElement.className = "scope-bar status-bar-item";
326 filterBarElement.id = "network-filter";
328 function createFilterElement(category, label)
330 var categoryElement = document.createElement("li");
331 categoryElement.category = category;
332 categoryElement.className = category;
333 categoryElement.appendChild(document.createTextNode(label));
334 categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
335 filterBarElement.appendChild(categoryElement);
337 return categoryElement;
340 this._filterAllElement = createFilterElement.call(this, "all", WebInspector.UIString("All"));
343 var dividerElement = document.createElement("div");
344 dividerElement.addStyleClass("scope-bar-divider");
345 filterBarElement.appendChild(dividerElement);
347 for (var category in this._categories)
348 createFilterElement.call(this, category, this._categories[category].title);
349 this._filterBarElement = filterBarElement;
352 _createSummaryBar: function()
354 var tbody = this._dataGrid.dataTableBody;
355 var tfoot = document.createElement("tfoot");
356 var tr = tfoot.createChild("tr", "revealed network-summary-bar");
357 var td = tr.createChild("td");
358 td.setAttribute("colspan", 7);
359 tbody.parentNode.insertBefore(tfoot, tbody);
360 this._summaryBarElement = td;
363 _updateSummaryBar: function()
365 var requestsNumber = this._resources.length;
367 if (!requestsNumber) {
368 if (this._summaryBarElement._isDisplayingWarning)
370 this._summaryBarElement._isDisplayingWarning = true;
372 var img = document.createElement("img");
373 img.src = "Images/warningIcon.png";
374 this._summaryBarElement.removeChildren();
375 this._summaryBarElement.appendChild(img);
376 this._summaryBarElement.appendChild(document.createTextNode(
377 WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity.")));
380 delete this._summaryBarElement._isDisplayingWarning;
382 var transferSize = 0;
383 var selectedRequestsNumber = 0;
384 var selectedTransferSize = 0;
387 for (var i = 0; i < this._resources.length; ++i) {
388 var resource = this._resources[i];
389 var resourceTransferSize = (resource.cached || !resource.transferSize) ? 0 : resource.transferSize;
390 transferSize += resourceTransferSize;
391 if (!this._hiddenCategories.all || !this._hiddenCategories[resource.category.name]) {
392 selectedRequestsNumber++;
393 selectedTransferSize += resourceTransferSize;
395 if (resource.url === WebInspector.inspectedPageURL) {
396 baseTime = resource.startTime;
397 WebInspector.mainResourceStartTime = resource.startTime;
399 if (resource.endTime > maxTime)
400 maxTime = resource.endTime;
403 if (this._hiddenCategories.all) {
404 text += String.sprintf(WebInspector.UIString("%d / %d requests"), selectedRequestsNumber, requestsNumber);
405 text += " \u2758 " + String.sprintf(WebInspector.UIString("%s / %s transferred"), Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize));
407 text += String.sprintf(WebInspector.UIString("%d requests"), requestsNumber);
408 text += " \u2758 " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize));
410 if (baseTime !== -1 && this._mainResourceLoadTime !== -1 && this._mainResourceDOMContentTime !== -1 && this._mainResourceDOMContentTime > baseTime) {
411 text += " \u2758 " + String.sprintf(WebInspector.UIString("%s (onload: %s, DOMContentLoaded: %s)"),
412 Number.secondsToString(maxTime - baseTime),
413 Number.secondsToString(this._mainResourceLoadTime - baseTime),
414 Number.secondsToString(this._mainResourceDOMContentTime - baseTime));
416 this._summaryBarElement.textContent = text;
419 _showCategory: function(category)
421 this._dataGrid.element.addStyleClass("filter-" + category);
422 delete this._hiddenCategories[category];
425 _hideCategory: function(category)
427 this._dataGrid.element.removeStyleClass("filter-" + category);
428 this._hiddenCategories[category] = true;
431 _updateFilter: function(e)
433 this._removeAllNodeHighlights();
434 var isMac = WebInspector.isMac();
435 var selectMultiple = false;
436 if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
437 selectMultiple = true;
438 if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
439 selectMultiple = true;
441 this._filter(e.target, selectMultiple);
442 this.performSearch(null, true);
443 this._updateSummaryBar();
446 _filter: function(target, selectMultiple)
448 function unselectAll()
450 for (var i = 0; i < this._filterBarElement.childNodes.length; ++i) {
451 var child = this._filterBarElement.childNodes[i];
455 child.removeStyleClass("selected");
456 this._hideCategory(child.category);
460 if (target.category === this._filterAllElement) {
461 if (target.hasStyleClass("selected")) {
462 // We can't unselect All, so we break early here
466 // If All wasn't selected, and now is, unselect everything else.
467 unselectAll.call(this);
469 // Something other than All is being selected, so we want to unselect All.
470 if (this._filterAllElement.hasStyleClass("selected")) {
471 this._filterAllElement.removeStyleClass("selected");
472 this._hideCategory("all");
476 if (!selectMultiple) {
477 // If multiple selection is off, we want to unselect everything else
478 // and just select ourselves.
479 unselectAll.call(this);
481 target.addStyleClass("selected");
482 this._showCategory(target.category);
483 this._updateOffscreenRows();
487 if (target.hasStyleClass("selected")) {
488 // If selectMultiple is turned on, and we were selected, we just
489 // want to unselect ourselves.
490 target.removeStyleClass("selected");
491 this._hideCategory(target.category);
493 // If selectMultiple is turned on, and we weren't selected, we just
494 // want to select ourselves.
495 target.addStyleClass("selected");
496 this._showCategory(target.category);
498 this._updateOffscreenRows();
501 _defaultRefreshDelay: 500,
503 _scheduleRefresh: function()
505 if (this._needsRefresh)
508 this._needsRefresh = true;
510 if (this.isShowing() && !this._refreshTimeout)
511 this._refreshTimeout = setTimeout(this.refresh.bind(this), this._defaultRefreshDelay);
514 _updateDividersIfNeeded: function(force)
518 var timelineColumn = this._dataGrid.columns.timeline;
519 for (var i = 0; i < this._dataGrid.resizers.length; ++i) {
520 if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) {
521 // Position timline grid location.
522 this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left;
523 this._timelineGrid.element.style.right = "18px";
528 if (!this.isShowing()) {
529 this._scheduleRefresh();
532 proceed = this._timelineGrid.updateDividers(force, this.calculator);
537 if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
538 // If our current sorting method starts at zero, that means it shows all
539 // resources starting at the same point, and so onLoad event and DOMContent
540 // event lines really wouldn't make much sense here, so don't render them.
541 // Additionally, if the calculator doesn't have the computePercentageFromEventTime
542 // function defined, we are probably sorting by size, and event times aren't relevant
547 this._timelineGrid.removeEventDividers();
548 if (this._mainResourceLoadTime !== -1) {
549 var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime);
551 var loadDivider = document.createElement("div");
552 loadDivider.className = "network-event-divider network-red-divider";
554 var loadDividerPadding = document.createElement("div");
555 loadDividerPadding.className = "network-event-divider-padding";
556 loadDividerPadding.title = WebInspector.UIString("Load event fired");
557 loadDividerPadding.appendChild(loadDivider);
558 loadDividerPadding.style.left = percent + "%";
559 this._timelineGrid.addEventDivider(loadDividerPadding);
562 if (this._mainResourceDOMContentTime !== -1) {
563 var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime);
565 var domContentDivider = document.createElement("div");
566 domContentDivider.className = "network-event-divider network-blue-divider";
568 var domContentDividerPadding = document.createElement("div");
569 domContentDividerPadding.className = "network-event-divider-padding";
570 domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
571 domContentDividerPadding.appendChild(domContentDivider);
572 domContentDividerPadding.style.left = percent + "%";
573 this._timelineGrid.addEventDivider(domContentDividerPadding);
577 _refreshIfNeeded: function()
579 if (this._needsRefresh)
583 _invalidateAllItems: function()
585 for (var i = 0; i < this._resources.length; ++i) {
586 var resource = this._resources[i];
587 this._staleResources[resource.requestId] = resource;
593 return this._calculator;
598 if (!x || this._calculator === x)
601 this._calculator = x;
602 this._calculator.reset();
604 this._invalidateAllItems();
608 _resourceGridNode: function(resource)
610 return this._resourceGridNodes[resource.__gridNodeId];
613 _createResourceGridNode: function(resource)
615 var node = new WebInspector.NetworkDataGridNode(this, resource);
616 resource.__gridNodeId = this._lastResourceGridNodeId++;
617 this._resourceGridNodes[resource.__gridNodeId] = node;
621 _createStatusbarButtons: function()
623 this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item");
624 this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked, this);
626 this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
627 this._clearButton.addEventListener("click", this._reset, this);
629 this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
630 this._largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows.get();
631 this._largerResourcesButton.addEventListener("click", this._toggleLargerResources, this);
634 _onLoadEventFired: function(event)
636 this._mainResourceLoadTime = event.data || -1;
637 // Schedule refresh to update boundaries and draw the new line.
638 this._scheduleRefresh();
641 _domContentLoadedEventFired: function(event)
643 this._mainResourceDOMContentTime = event.data || -1;
644 // Schedule refresh to update boundaries and draw the new line.
645 this._scheduleRefresh();
650 this._refreshIfNeeded();
655 this._popoverHelper.hidePopover();
660 this._needsRefresh = false;
661 if (this._refreshTimeout) {
662 clearTimeout(this._refreshTimeout);
663 delete this._refreshTimeout;
666 this._removeAllNodeHighlights();
667 var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow();
668 var boundariesChanged = false;
669 if (this.calculator.updateBoundariesForEventTime) {
670 boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainResourceLoadTime) || boundariesChanged;
671 boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainResourceDOMContentTime) || boundariesChanged;
674 for (var resourceId in this._staleResources) {
675 var resource = this._staleResources[resourceId];
676 var node = this._resourceGridNode(resource);
678 // Create the timeline tree element and graph.
679 node = this._createResourceGridNode(resource);
680 this._dataGrid.appendChild(node);
682 node.refreshResource();
684 if (this.calculator.updateBoundaries(resource))
685 boundariesChanged = true;
687 if (!node.isFilteredOut())
688 this._updateHighlightIfMatched(resource);
691 if (boundariesChanged) {
692 // The boundaries changed, so all item graphs are stale.
693 this._invalidateAllItems();
696 for (var resourceId in this._staleResources)
697 this._resourceGridNode(this._staleResources[resourceId]).refreshGraph(this.calculator);
699 this._staleResources = {};
701 this._updateSummaryBar();
702 this._dataGrid.updateWidths();
703 // FIXME: evaluate performance impact of moving this before a call to sortItems()
704 if (wasScrolledToLastRow)
705 this._dataGrid.scrollToLastRow();
708 _onPreserveLogClicked: function(e)
710 this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled;
715 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared);
717 this._clearSearchMatchedList();
718 if (this._popoverHelper)
719 this._popoverHelper.hidePopover();
721 if (this._calculator)
722 this._calculator.reset();
724 this._resources = [];
725 this._resourcesById = {};
726 this._resourcesByURL = {};
727 this._staleResources = {};
728 this._resourceGridNodes = {};
730 if (this._dataGrid) {
731 this._dataGrid.removeChildren();
732 this._updateDividersIfNeeded(true);
733 this._updateSummaryBar();
736 this._mainResourceLoadTime = -1;
737 this._mainResourceDOMContentTime = -1;
738 this._linkifier.reset();
743 return this._resources;
746 resourceById: function(id)
748 return this._resourcesById[id];
751 _onResourceStarted: function(event)
753 this._appendResource(event.data);
756 _appendResource: function(resource)
758 this._resources.push(resource);
760 // In case of redirect request id is reassigned to a redirected
761 // resource and we need to update _resourcesById ans search results.
762 if (this._resourcesById[resource.requestId]) {
763 var oldResource = resource.redirects[resource.redirects.length - 1];
764 this._resourcesById[oldResource.requestId] = oldResource;
766 this._updateSearchMatchedListAfterRequestIdChanged(resource.requestId, oldResource.requestId);
768 this._resourcesById[resource.requestId] = resource;
770 this._resourcesByURL[resource.url] = resource;
772 // Pull all the redirects of the main resource upon commit load.
773 if (resource.redirects) {
774 for (var i = 0; i < resource.redirects.length; ++i)
775 this._refreshResource(resource.redirects[i]);
778 this._refreshResource(resource);
781 _onResourceUpdated: function(event)
783 this._refreshResource(event.data);
786 _refreshResource: function(resource)
788 this._staleResources[resource.requestId] = resource;
789 this._scheduleRefresh();
794 if (this._preserveLogToggle.toggled)
799 _mainFrameNavigated: function(event)
801 if (this._preserveLogToggle.toggled)
804 var frame = /** @type {WebInspector.ResourceTreeFrame} */ event.data;
805 var loaderId = frame.loaderId;
807 // Preserve provisional load resources.
808 var resourcesToPreserve = [];
809 for (var i = 0; i < this._resources.length; ++i) {
810 var resource = this._resources[i];
811 if (resource.loaderId === loaderId)
812 resourcesToPreserve.push(resource);
817 // Restore preserved items.
818 for (var i = 0; i < resourcesToPreserve.length; ++i)
819 this._appendResource(resourcesToPreserve[i]);
822 switchToDetailedView: function()
826 if (this._dataGrid.selectedNode)
827 this._dataGrid.selectedNode.selected = false;
829 this.element.removeStyleClass("brief-mode");
831 this._dataGrid.showColumn("method");
832 this._dataGrid.showColumn("status");
833 this._dataGrid.showColumn("type");
834 if (Capabilities.nativeInstrumentationEnabled)
835 this._dataGrid.showColumn("initiator");
836 this._dataGrid.showColumn("size");
837 this._dataGrid.showColumn("time");
838 this._dataGrid.showColumn("timeline");
845 if (Capabilities.nativeInstrumentationEnabled)
846 widths.initiator = 10;
849 if (Capabilities.nativeInstrumentationEnabled)
850 widths.timeline = 40;
852 widths.timeline = 50;
854 this._dataGrid.applyColumnWidthsMap(widths);
857 switchToBriefView: function()
859 this.element.addStyleClass("brief-mode");
860 this._removeAllNodeHighlights();
862 this._dataGrid.hideColumn("method");
863 this._dataGrid.hideColumn("status");
864 this._dataGrid.hideColumn("type");
865 if (Capabilities.nativeInstrumentationEnabled)
866 this._dataGrid.hideColumn("initiator");
867 this._dataGrid.hideColumn("size");
868 this._dataGrid.hideColumn("time");
869 this._dataGrid.hideColumn("timeline");
873 this._dataGrid.applyColumnWidthsMap(widths);
875 this._popoverHelper.hidePopover();
878 _toggleLargerResources: function()
880 WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get());
881 this._setLargerResources(WebInspector.settings.resourcesLargeRows.get());
884 _setLargerResources: function(enabled)
886 this._largerResourcesButton.toggled = enabled;
888 this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
889 this._dataGrid.element.addStyleClass("small");
890 this._timelineGrid.element.addStyleClass("small");
892 this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
893 this._dataGrid.element.removeStyleClass("small");
894 this._timelineGrid.element.removeStyleClass("small");
896 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: enabled });
897 this._updateOffscreenRows();
900 _getPopoverAnchor: function(element)
902 if (!this._allowPopover)
904 var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
907 var resource = anchor.parentElement.resource;
908 return resource && resource.timing ? anchor : null;
911 _showPopover: function(anchor, popover)
913 var resource = anchor.parentElement.resource;
914 var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource);
915 popover.show(tableElement, anchor);
918 _contextMenu: function(event)
920 var contextMenu = new WebInspector.ContextMenu();
921 var gridNode = this._dataGrid.dataGridNodeFromNode(event.target);
922 var resource = gridNode && gridNode._resource;
925 contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, resource.url, false));
926 contextMenu.appendSeparator();
927 contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, resource));
928 if (resource.requestHeadersText)
929 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, resource));
930 if (resource.responseHeadersText)
931 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, resource));
932 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy entry as HAR" : "Copy Entry as HAR"), this._copyResource.bind(this, resource));
934 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this));
936 if (Preferences.saveAsAvailable) {
937 contextMenu.appendSeparator();
939 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save entry as HAR" : "Save Entry as HAR"), this._exportResource.bind(this, resource));
940 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save all as HAR" : "Save All as HAR"), this._exportAll.bind(this));
943 if (Capabilities.canClearCacheAndCookies) {
944 contextMenu.appendSeparator();
945 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this));
946 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this));
949 contextMenu.show(event);
955 log: (new WebInspector.HARLog(this._resources)).build()
957 InspectorFrontendHost.copyText(JSON.stringify(harArchive));
960 _copyResource: function(resource)
962 var har = (new WebInspector.HAREntry(resource)).build();
963 InspectorFrontendHost.copyText(JSON.stringify(har));
966 _copyLocation: function(resource)
968 InspectorFrontendHost.copyText(resource.url);
971 _copyRequestHeaders: function(resource)
973 InspectorFrontendHost.copyText(resource.requestHeadersText);
976 _copyResponseHeaders: function(resource)
978 InspectorFrontendHost.copyText(resource.responseHeadersText);
981 _exportAll: function()
984 log: (new WebInspector.HARLog(this._resources)).build()
987 InspectorFrontendHost.saveAs(WebInspector.inspectedPageDomain + ".har", JSON.stringify(harArchive));
990 _exportResource: function(resource)
992 var har = (new WebInspector.HAREntry(resource)).build();
993 InspectorFrontendHost.saveAs(resource.displayName + ".har", JSON.stringify(har));
996 _clearBrowserCache: function(event)
998 if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?")))
999 NetworkAgent.clearBrowserCache();
1002 _clearBrowserCookies: function(event)
1004 if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?")))
1005 NetworkAgent.clearBrowserCookies();
1008 _updateOffscreenRows: function()
1010 var dataTableBody = this._dataGrid.dataTableBody;
1011 var rows = dataTableBody.children;
1012 var recordsCount = rows.length;
1013 if (recordsCount < 2)
1014 return; // Filler row only.
1016 var visibleTop = this._dataGrid.scrollContainer.scrollTop;
1017 var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight;
1021 // Filler is at recordsCount - 1.
1022 var unfilteredRowIndex = 0;
1023 for (var i = 0; i < recordsCount - 1; ++i) {
1026 var dataGridNode = this._dataGrid.dataGridNodeFromNode(row);
1027 if (dataGridNode.isFilteredOut()) {
1028 row.removeStyleClass("offscreen");
1033 rowHeight = row.offsetHeight;
1035 var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop;
1036 if (rowIsVisible !== row.rowIsVisible) {
1038 row.removeStyleClass("offscreen");
1040 row.addStyleClass("offscreen");
1041 row.rowIsVisible = rowIsVisible;
1043 unfilteredRowIndex++;
1047 _matchResource: function(resource)
1049 if (!this._searchRegExp)
1052 if ((!resource.displayName || !resource.displayName.match(this._searchRegExp)) && !resource.folder.match(this._searchRegExp))
1055 if (resource.requestId in this._matchedResourcesMap)
1056 return this._matchedResourcesMap[resource.requestId];
1058 var matchedResourceIndex = this._matchedResources.length;
1059 this._matchedResourcesMap[resource.requestId] = matchedResourceIndex;
1060 this._matchedResources.push(resource.requestId);
1062 return matchedResourceIndex;
1065 _clearSearchMatchedList: function()
1067 this._matchedResources = [];
1068 this._matchedResourcesMap = {};
1069 this._highlightNthMatchedResource(-1, false);
1072 _updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId)
1074 var resourceIndex = this._matchedResourcesMap[oldRequestId];
1075 if (resourceIndex) {
1076 delete this._matchedResourcesMap[oldRequestId];
1077 this._matchedResourcesMap[newRequestId] = resourceIndex;
1078 this._matchedResources[resourceIndex] = newRequestId;
1082 _updateHighlightIfMatched: function(resource)
1084 var matchedResourceIndex = this._matchResource(resource);
1085 if (matchedResourceIndex === -1)
1088 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedResources.length);
1090 if (this._currentMatchedResourceIndex !== -1 && this._currentMatchedResourceIndex !== matchedResourceIndex)
1093 this._highlightNthMatchedResource(matchedResourceIndex, false);
1096 _highlightNthMatchedResource: function(matchedResourceIndex, reveal)
1098 if (this._highlightedSubstringChanges) {
1099 revertDomChanges(this._highlightedSubstringChanges);
1100 this._highlightedSubstringChanges = null;
1103 if (matchedResourceIndex === -1) {
1104 this._currentMatchedResourceIndex = matchedResourceIndex;
1108 var resource = this._resourcesById[this._matchedResources[matchedResourceIndex]];
1112 var nameMatched = resource.displayName && resource.displayName.match(this._searchRegExp);
1113 var pathMatched = resource.path && resource.folder.match(this._searchRegExp);
1114 if (!nameMatched && pathMatched && !this._largerResourcesButton.toggled)
1115 this._toggleLargerResources();
1117 var node = this._resourceGridNode(resource);
1119 this._highlightedSubstringChanges = node._highlightMatchedSubstring(this._searchRegExp);
1122 this._currentMatchedResourceIndex = matchedResourceIndex;
1124 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedResourceIndex);
1127 performSearch: function(searchQuery, sortOrFilterApplied)
1129 var newMatchedResourceIndex = 0;
1130 var currentMatchedRequestId;
1131 if (this._currentMatchedResourceIndex !== -1)
1132 currentMatchedRequestId = this._matchedResources[this._currentMatchedResourceIndex];
1134 if (!sortOrFilterApplied)
1135 this._searchRegExp = createPlainTextSearchRegex(searchQuery, "i");
1137 this._clearSearchMatchedList();
1139 var childNodes = this._dataGrid.dataTableBody.childNodes;
1140 var resourceNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row.
1142 for (var i = 0; i < resourceNodes.length; ++i) {
1143 var dataGridNode = this._dataGrid.dataGridNodeFromNode(resourceNodes[i]);
1144 if (dataGridNode.isFilteredOut())
1147 if (this._matchResource(dataGridNode._resource) !== -1 && dataGridNode._resource.requestId === currentMatchedRequestId)
1148 newMatchedResourceIndex = this._matchedResources.length - 1;
1151 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedResources.length);
1152 this._highlightNthMatchedResource(newMatchedResourceIndex, !sortOrFilterApplied);
1155 jumpToPreviousSearchResult: function()
1157 if (!this._matchedResources.length)
1159 this._highlightNthMatchedResource((this._currentMatchedResourceIndex + this._matchedResources.length - 1) % this._matchedResources.length, true);
1162 jumpToNextSearchResult: function()
1164 if (!this._matchedResources.length)
1166 this._highlightNthMatchedResource((this._currentMatchedResourceIndex + 1) % this._matchedResources.length, true);
1169 searchCanceled: function()
1171 this._clearSearchMatchedList();
1172 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, 0);
1175 revealAndHighlightResource: function(resource)
1177 this._removeAllNodeHighlights();
1179 var node = this._resourceGridNode(resource);
1181 this._dataGrid.element.focus();
1183 this._highlightNode(node);
1187 _removeAllNodeHighlights: function()
1189 if (this._highlightedNode) {
1190 this._highlightedNode.element.removeStyleClass("highlighted-row");
1191 delete this._highlightedNode;
1195 _highlightNode: function(node)
1197 node.element.addStyleClass("highlighted-row");
1198 this._highlightedNode = node;
1202 WebInspector.NetworkLogView.prototype.__proto__ = WebInspector.View.prototype;
1204 WebInspector.NetworkLogView.EventTypes = {
1205 ViewCleared: "ViewCleared",
1206 RowSizeChanged: "RowSizeChanged",
1207 ResourceSelected: "ResourceSelected",
1208 SearchCountUpdated: "SearchCountUpdated",
1209 SearchIndexUpdated: "SearchIndexUpdated"
1214 * @extends {WebInspector.Panel}
1216 WebInspector.NetworkPanel = function()
1218 WebInspector.Panel.call(this, "network");
1219 this.registerRequiredCSS("networkPanel.css");
1221 this.createSplitView();
1222 this.splitView.hideMainElement();
1224 this._networkLogView = new WebInspector.NetworkLogView();
1225 this._networkLogView.show(this.sidebarElement);
1227 this._viewsContainerElement = this.splitView.mainElement;
1228 this._viewsContainerElement.id = "network-views";
1229 this._viewsContainerElement.addStyleClass("hidden");
1230 if (!this._networkLogView.useLargeRows)
1231 this._viewsContainerElement.addStyleClass("small");
1233 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ViewCleared, this._onViewCleared, this);
1234 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, this._onRowSizeChanged, this);
1235 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ResourceSelected, this._onResourceSelected, this);
1236 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._onSearchCountUpdated, this);
1237 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._onSearchIndexUpdated, this);
1239 this._closeButtonElement = document.createElement("button");
1240 this._closeButtonElement.id = "network-close-button";
1241 this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false);
1242 this._viewsContainerElement.appendChild(this._closeButtonElement);
1244 function viewGetter()
1246 return this.visibleView;
1248 WebInspector.GoToLineDialog.install(this, viewGetter.bind(this));
1251 WebInspector.NetworkPanel.prototype = {
1252 get toolbarItemLabel()
1254 return WebInspector.UIString("Network");
1257 get statusBarItems()
1259 return this._networkLogView.statusBarItems;
1262 elementsToRestoreScrollPositionsFor: function()
1264 return this._networkLogView.elementsToRestoreScrollPositionsFor();
1267 // FIXME: only used by the layout tests, should not be exposed.
1270 this._networkLogView._reset();
1273 handleShortcut: function(event)
1275 if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
1276 this._toggleGridMode();
1277 event.handled = true;
1281 WebInspector.Panel.prototype.handleShortcut.call(this, event);
1284 wasShown: function()
1286 WebInspector.Panel.prototype.wasShown.call(this);
1291 return this._networkLogView.resources;
1294 resourceById: function(id)
1296 return this._networkLogView.resourceById(id);
1299 _resourceByAnchor: function(anchor)
1302 if (anchor.getAttribute("request_id"))
1303 resource = this.resourceById(anchor.getAttribute("request_id"));
1305 resource = this._networkLogView._resourcesByURL[anchor.href];
1310 canShowAnchorLocation: function(anchor)
1312 return !!this._resourceByAnchor(anchor);
1315 showAnchorLocation: function(anchor)
1317 var resource = this._resourceByAnchor(anchor);
1318 this.revealAndHighlightResource(resource)
1321 revealAndHighlightResource: function(resource)
1323 this._toggleGridMode();
1325 this._networkLogView.revealAndHighlightResource(resource);
1328 _onViewCleared: function(event)
1330 this._closeVisibleResource();
1331 this._toggleGridMode();
1332 this._viewsContainerElement.removeChildren();
1333 this._viewsContainerElement.appendChild(this._closeButtonElement);
1336 _onRowSizeChanged: function(event)
1338 if (event.data.largeRows)
1339 this._viewsContainerElement.removeStyleClass("small");
1341 this._viewsContainerElement.addStyleClass("small");
1344 _onSearchCountUpdated: function(event)
1346 WebInspector.searchController.updateSearchMatchesCount(event.data, this);
1349 _onSearchIndexUpdated: function(event)
1351 WebInspector.searchController.updateCurrentMatchIndex(event.data, this);
1354 _onResourceSelected: function(event)
1356 this._showResource(event.data);
1359 _showResource: function(resource)
1364 this._toggleViewingResourceMode();
1366 if (this.visibleView) {
1367 this.visibleView.detach();
1368 delete this.visibleView;
1371 var view = new WebInspector.NetworkItemView(resource);
1372 view.show(this._viewsContainerElement);
1373 this.visibleView = view;
1376 _closeVisibleResource: function()
1378 this.element.removeStyleClass("viewing-resource");
1380 if (this.visibleView) {
1381 this.visibleView.detach();
1382 delete this.visibleView;
1386 _toggleGridMode: function()
1388 if (this._viewingResourceMode) {
1389 this._viewingResourceMode = false;
1390 this.element.removeStyleClass("viewing-resource");
1391 this.splitView.hideMainElement();
1394 this._networkLogView.switchToDetailedView();
1395 this._networkLogView.allowPopover = true;
1396 this._networkLogView.allowResourceSelection = false;
1399 _toggleViewingResourceMode: function()
1401 if (this._viewingResourceMode)
1403 this._viewingResourceMode = true;
1405 this.element.addStyleClass("viewing-resource");
1406 this.splitView.showMainElement();
1407 this._networkLogView.allowPopover = false;
1408 this._networkLogView.allowResourceSelection = true;
1409 this._networkLogView.switchToBriefView();
1412 performSearch: function(searchQuery, sortOrFilterApplied)
1414 this._networkLogView.performSearch(searchQuery, sortOrFilterApplied);
1417 jumpToPreviousSearchResult: function()
1419 this._networkLogView.jumpToPreviousSearchResult();
1422 jumpToNextSearchResult: function()
1424 this._networkLogView.jumpToNextSearchResult();
1427 searchCanceled: function()
1429 this._networkLogView.searchCanceled();
1433 WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1438 WebInspector.NetworkBaseCalculator = function()
1442 WebInspector.NetworkBaseCalculator.prototype = {
1443 computeBarGraphPercentages: function(item)
1445 return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
1448 computeBarGraphLabels: function(item)
1450 const label = this.formatValue(this._value(item));
1451 return {left: label, right: label, tooltip: label};
1456 return this.maximumBoundary - this.minimumBoundary;
1459 updateBoundaries: function(item)
1461 this.minimumBoundary = 0;
1463 var value = this._value(item);
1464 if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
1465 this.maximumBoundary = value;
1473 delete this.minimumBoundary;
1474 delete this.maximumBoundary;
1477 _value: function(item)
1482 formatValue: function(value)
1484 return value.toString();
1490 * @extends {WebInspector.NetworkBaseCalculator}
1492 WebInspector.NetworkTimeCalculator = function(startAtZero)
1494 WebInspector.NetworkBaseCalculator.call(this);
1495 this.startAtZero = startAtZero;
1498 WebInspector.NetworkTimeCalculator.prototype = {
1499 computeBarGraphPercentages: function(resource)
1501 if (resource.startTime !== -1)
1502 var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
1506 if (resource.responseReceivedTime !== -1)
1507 var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
1509 var middle = (this.startAtZero ? start : 100);
1511 if (resource.endTime !== -1)
1512 var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
1514 var end = (this.startAtZero ? middle : 100);
1516 if (this.startAtZero) {
1522 return {start: start, middle: middle, end: end};
1525 computePercentageFromEventTime: function(eventTime)
1527 // This function computes a percentage in terms of the total loading time
1528 // of a specific event. If startAtZero is set, then this is useless, and we
1529 // want to return 0.
1530 if (eventTime !== -1 && !this.startAtZero)
1531 return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
1536 updateBoundariesForEventTime: function(eventTime)
1538 if (eventTime === -1 || this.startAtZero)
1541 if (typeof this.maximumBoundary === "undefined" || eventTime > this.maximumBoundary) {
1542 this.maximumBoundary = eventTime;
1548 computeBarGraphLabels: function(resource)
1550 var rightLabel = "";
1551 if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
1552 rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
1554 var hasLatency = resource.latency > 0;
1556 var leftLabel = this.formatValue(resource.latency);
1558 var leftLabel = rightLabel;
1560 if (resource.timing)
1561 return {left: leftLabel, right: rightLabel};
1563 if (hasLatency && rightLabel) {
1564 var total = this.formatValue(resource.duration);
1565 var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1566 } else if (hasLatency)
1567 var tooltip = WebInspector.UIString("%s latency", leftLabel);
1568 else if (rightLabel)
1569 var tooltip = WebInspector.UIString("%s download", rightLabel);
1571 if (resource.cached)
1572 tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1573 return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1576 updateBoundaries: function(resource)
1578 var didChange = false;
1581 if (this.startAtZero)
1584 lowerBound = this._lowerBound(resource);
1586 if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1587 this.minimumBoundary = lowerBound;
1591 var upperBound = this._upperBound(resource);
1592 if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1593 this.maximumBoundary = upperBound;
1600 formatValue: function(value)
1602 return Number.secondsToString(value);
1605 _lowerBound: function(resource)
1610 _upperBound: function(resource)
1616 WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype;
1620 * @extends {WebInspector.NetworkTimeCalculator}
1622 WebInspector.NetworkTransferTimeCalculator = function()
1624 WebInspector.NetworkTimeCalculator.call(this, false);
1627 WebInspector.NetworkTransferTimeCalculator.prototype = {
1628 formatValue: function(value)
1630 return Number.secondsToString(value);
1633 _lowerBound: function(resource)
1635 return resource.startTime;
1638 _upperBound: function(resource)
1640 return resource.endTime;
1644 WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1648 * @extends {WebInspector.NetworkTimeCalculator}
1650 WebInspector.NetworkTransferDurationCalculator = function()
1652 WebInspector.NetworkTimeCalculator.call(this, true);
1655 WebInspector.NetworkTransferDurationCalculator.prototype = {
1656 formatValue: function(value)
1658 return Number.secondsToString(value);
1661 _upperBound: function(resource)
1663 return resource.duration;
1667 WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1671 * @extends {WebInspector.DataGridNode}
1673 WebInspector.NetworkDataGridNode = function(parentView, resource)
1675 WebInspector.DataGridNode.call(this, {});
1676 this._parentView = parentView;
1677 this._resource = resource;
1680 WebInspector.NetworkDataGridNode.prototype = {
1681 createCells: function()
1683 // Out of sight, out of mind: create nodes offscreen to save on render tree update times when running updateOffscreenRows()
1684 this._element.addStyleClass("offscreen");
1685 this._nameCell = this._createDivInTD("name");
1686 this._methodCell = this._createDivInTD("method");
1687 this._statusCell = this._createDivInTD("status");
1688 this._typeCell = this._createDivInTD("type");
1689 if (Capabilities.nativeInstrumentationEnabled)
1690 this._initiatorCell = this._createDivInTD("initiator");
1691 this._sizeCell = this._createDivInTD("size");
1692 this._timeCell = this._createDivInTD("time");
1693 this._createTimelineCell();
1694 this._nameCell.addEventListener("click", this.select.bind(this), false);
1695 this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false);
1698 isFilteredOut: function()
1700 if (!this._parentView._hiddenCategories.all)
1702 return this._resource.category.name in this._parentView._hiddenCategories;
1707 this._parentView.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ResourceSelected, this._resource);
1708 WebInspector.DataGridNode.prototype.select.apply(this, arguments);
1711 _highlightMatchedSubstring: function(regexp)
1713 var domChanges = [];
1714 var matchInfo = this._nameCell.textContent.match(regexp);
1715 highlightSearchResult(this._nameCell, matchInfo.index, matchInfo[0].length, domChanges);
1719 _openInNewTab: function()
1721 PageAgent.open(this._resource.url, true);
1726 return this._parentView.allowResourceSelection && !this.isFilteredOut();
1729 _createDivInTD: function(columnIdentifier)
1731 var td = document.createElement("td");
1732 td.className = columnIdentifier + "-column";
1733 var div = document.createElement("div");
1734 td.appendChild(div);
1735 this._element.appendChild(td);
1739 _createTimelineCell: function()
1741 this._graphElement = document.createElement("div");
1742 this._graphElement.className = "network-graph-side";
1744 this._barAreaElement = document.createElement("div");
1745 // this._barAreaElement.className = "network-graph-bar-area hidden";
1746 this._barAreaElement.className = "network-graph-bar-area";
1747 this._barAreaElement.resource = this._resource;
1748 this._graphElement.appendChild(this._barAreaElement);
1750 this._barLeftElement = document.createElement("div");
1751 this._barLeftElement.className = "network-graph-bar waiting";
1752 this._barAreaElement.appendChild(this._barLeftElement);
1754 this._barRightElement = document.createElement("div");
1755 this._barRightElement.className = "network-graph-bar";
1756 this._barAreaElement.appendChild(this._barRightElement);
1759 this._labelLeftElement = document.createElement("div");
1760 this._labelLeftElement.className = "network-graph-label waiting";
1761 this._barAreaElement.appendChild(this._labelLeftElement);
1763 this._labelRightElement = document.createElement("div");
1764 this._labelRightElement.className = "network-graph-label";
1765 this._barAreaElement.appendChild(this._labelRightElement);
1767 this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);
1769 this._timelineCell = document.createElement("td");
1770 this._timelineCell.className = "timeline-column";
1771 this._element.appendChild(this._timelineCell);
1772 this._timelineCell.appendChild(this._graphElement);
1775 refreshResource: function()
1777 this._refreshNameCell();
1779 this._methodCell.setTextAndTitle(this._resource.requestMethod);
1781 this._refreshStatusCell();
1782 this._refreshTypeCell();
1783 if (Capabilities.nativeInstrumentationEnabled)
1784 this._refreshInitiatorCell();
1785 this._refreshSizeCell();
1786 this._refreshTimeCell();
1788 if (this._resource.cached)
1789 this._graphElement.addStyleClass("resource-cached");
1791 this._element.addStyleClass("network-item");
1792 if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) {
1793 this._element.removeMatchingStyleClasses("network-category-\\w+");
1794 this._element.addStyleClass("network-category-" + this._resource.category.name);
1798 _refreshNameCell: function()
1800 this._nameCell.removeChildren();
1802 if (this._resource.category === WebInspector.resourceCategories.images) {
1803 var previewImage = document.createElement("img");
1804 previewImage.className = "image-network-icon-preview";
1805 this._resource.populateImageSource(previewImage);
1807 var iconElement = document.createElement("div");
1808 iconElement.className = "icon";
1809 iconElement.appendChild(previewImage);
1811 var iconElement = document.createElement("img");
1812 iconElement.className = "icon";
1814 this._nameCell.appendChild(iconElement);
1815 this._nameCell.appendChild(document.createTextNode(this._fileName()));
1818 var subtitle = this._resource.displayDomain;
1820 if (this._resource.path)
1821 subtitle += this._resource.folder;
1823 this._appendSubtitle(this._nameCell, subtitle);
1824 this._nameCell.title = this._resource.url;
1827 _fileName: function()
1829 var fileName = this._resource.displayName;
1830 if (this._resource.queryString)
1831 fileName += "?" + this._resource.queryString;
1835 _refreshStatusCell: function()
1837 this._statusCell.removeChildren();
1839 if (this._resource.failed) {
1840 if (this._resource.canceled)
1841 this._statusCell.setTextAndTitle(WebInspector.UIString("(canceled)"));
1843 this._statusCell.setTextAndTitle(WebInspector.UIString("(failed)"));
1844 this._statusCell.addStyleClass("network-dim-cell");
1845 this.element.addStyleClass("network-error-row");
1849 this._statusCell.removeStyleClass("network-dim-cell");
1850 this.element.removeStyleClass("network-error-row");
1852 if (this._resource.statusCode) {
1853 this._statusCell.appendChild(document.createTextNode(this._resource.statusCode));
1854 this._appendSubtitle(this._statusCell, this._resource.statusText);
1855 this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText;
1856 if (this._resource.statusCode >= 400)
1857 this.element.addStyleClass("network-error-row");
1858 if (this._resource.cached)
1859 this._statusCell.addStyleClass("network-dim-cell");
1861 if (!this._resource.isHttpFamily() && this._resource.finished)
1862 this._statusCell.setTextAndTitle(WebInspector.UIString("Success"));
1863 else if (this._resource.isPingRequest())
1864 this._statusCell.setTextAndTitle(WebInspector.UIString("(ping)"));
1866 this._statusCell.setTextAndTitle(WebInspector.UIString("(pending)"));
1867 this._statusCell.addStyleClass("network-dim-cell");
1871 _refreshTypeCell: function()
1873 if (this._resource.mimeType) {
1874 this._typeCell.removeStyleClass("network-dim-cell");
1875 this._typeCell.setTextAndTitle(this._resource.mimeType);
1876 } else if (this._resource.isPingRequest) {
1877 this._typeCell.removeStyleClass("network-dim-cell");
1878 this._typeCell.setTextAndTitle(this._resource.requestContentType());
1880 this._typeCell.addStyleClass("network-dim-cell");
1881 this._typeCell.setTextAndTitle(WebInspector.UIString("Pending"));
1885 _refreshInitiatorCell: function()
1887 var initiator = this._resource.initiator;
1888 if ((initiator && initiator.type !== "other") || this._resource.redirectSource) {
1889 this._initiatorCell.removeStyleClass("network-dim-cell");
1890 this._initiatorCell.removeChildren();
1891 if (this._resource.redirectSource) {
1892 var redirectSource = this._resource.redirectSource;
1893 var anchor = WebInspector.linkifyURLAsNode(redirectSource.url, redirectSource.url, undefined, false);
1894 anchor.setAttribute("request_id", redirectSource.requestId);
1895 anchor.setAttribute("preferred_panel", "network");
1896 this._initiatorCell.title = redirectSource.url;
1897 this._initiatorCell.appendChild(anchor);
1898 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Redirect"));
1899 } else if (initiator.type === "script") {
1900 var topFrame = initiator.stackTrace[0];
1901 // This could happen when resource loading was triggered by console.
1902 if (!topFrame.url) {
1903 this._initiatorCell.addStyleClass("network-dim-cell");
1904 this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
1907 this._initiatorCell.title = topFrame.url + ":" + topFrame.lineNumber;
1908 var urlElement = this._parentView._linkifier.linkifyLocation(topFrame.url, topFrame.lineNumber - 1, 0);
1909 this._initiatorCell.appendChild(urlElement);
1910 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Script"));
1911 } else { // initiator.type === "parser"
1912 this._initiatorCell.title = initiator.url + ":" + initiator.lineNumber;
1913 this._initiatorCell.appendChild(WebInspector.linkifyResourceAsNode(initiator.url, initiator.lineNumber - 1));
1914 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Parser"));
1917 this._initiatorCell.addStyleClass("network-dim-cell");
1918 this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
1922 _refreshSizeCell: function()
1924 if (this._resource.cached) {
1925 this._sizeCell.setTextAndTitle(WebInspector.UIString("(from cache)"));
1926 this._sizeCell.addStyleClass("network-dim-cell");
1928 var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?";
1929 var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?";
1930 this._sizeCell.setTextAndTitle(transferSize);
1931 this._sizeCell.removeStyleClass("network-dim-cell");
1932 this._appendSubtitle(this._sizeCell, resourceSize);
1936 _refreshTimeCell: function()
1938 if (this._resource.duration > 0) {
1939 this._timeCell.removeStyleClass("network-dim-cell");
1940 this._timeCell.setTextAndTitle(Number.secondsToString(this._resource.duration));
1941 this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency));
1943 this._timeCell.addStyleClass("network-dim-cell");
1944 this._timeCell.setTextAndTitle(WebInspector.UIString("Pending"));
1948 _appendSubtitle: function(cellElement, subtitleText)
1950 var subtitleElement = document.createElement("div");
1951 subtitleElement.className = "network-cell-subtitle";
1952 subtitleElement.textContent = subtitleText;
1953 cellElement.appendChild(subtitleElement);
1956 refreshGraph: function(calculator)
1958 var percentages = calculator.computeBarGraphPercentages(this._resource);
1959 this._percentages = percentages;
1961 this._barAreaElement.removeStyleClass("hidden");
1963 if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) {
1964 this._graphElement.removeMatchingStyleClasses("network-category-\\w+");
1965 this._graphElement.addStyleClass("network-category-" + this._resource.category.name);
1968 this._barLeftElement.style.setProperty("left", percentages.start + "%");
1969 this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
1971 this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
1972 this._barRightElement.style.setProperty("left", percentages.middle + "%");
1974 var labels = calculator.computeBarGraphLabels(this._resource);
1975 this._labelLeftElement.textContent = labels.left;
1976 this._labelRightElement.textContent = labels.right;
1978 var tooltip = (labels.tooltip || "");
1979 this._barLeftElement.title = tooltip;
1980 this._labelLeftElement.title = tooltip;
1981 this._labelRightElement.title = tooltip;
1982 this._barRightElement.title = tooltip;
1985 _refreshLabelPositions: function()
1987 if (!this._percentages)
1989 this._labelLeftElement.style.removeProperty("left");
1990 this._labelLeftElement.style.removeProperty("right");
1991 this._labelLeftElement.removeStyleClass("before");
1992 this._labelLeftElement.removeStyleClass("hidden");
1994 this._labelRightElement.style.removeProperty("left");
1995 this._labelRightElement.style.removeProperty("right");
1996 this._labelRightElement.removeStyleClass("after");
1997 this._labelRightElement.removeStyleClass("hidden");
1999 const labelPadding = 10;
2000 const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
2001 const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
2003 if (this._barLeftElement) {
2004 var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
2005 var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
2007 var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
2008 var rightBarWidth = barRightElementOffsetWidth - labelPadding;
2011 const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
2012 const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
2014 const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
2015 const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
2016 const graphElementOffsetWidth = this._graphElement.offsetWidth;
2018 if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
2019 var leftHidden = true;
2021 if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
2022 var rightHidden = true;
2024 if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
2025 // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
2026 if (labelBefore && !labelAfter)
2028 else if (labelAfter && !labelBefore)
2034 this._labelLeftElement.addStyleClass("hidden");
2035 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
2036 this._labelLeftElement.addStyleClass("before");
2038 this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
2039 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
2044 this._labelRightElement.addStyleClass("hidden");
2045 this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
2046 this._labelRightElement.addStyleClass("after");
2048 this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
2049 this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
2054 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
2056 var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : "");
2057 var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : "");
2058 if (aFileName > bFileName)
2060 if (bFileName > aFileName)
2065 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
2067 if (b._resource.cached && !a._resource.cached)
2069 if (a._resource.cached && !b._resource.cached)
2072 if (a._resource.resourceSize === b._resource.resourceSize)
2075 return a._resource.resourceSize - b._resource.resourceSize;
2078 WebInspector.NetworkDataGridNode.InitiatorComparator = function(a, b)
2080 if (!a._resource.initiator || a._resource.initiator.type === "Other")
2082 if (!b._resource.initiator || b._resource.initiator.type === "Other")
2085 if (a._resource.initiator.url < b._resource.initiator.url)
2087 if (a._resource.initiator.url > b._resource.initiator.url)
2090 return a._resource.initiator.lineNumber - b._resource.initiator.lineNumber;
2093 WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b)
2095 var aValue = a._resource[propertyName];
2096 var bValue = b._resource[propertyName];
2097 if (aValue > bValue)
2098 return revert ? -1 : 1;
2099 if (bValue > aValue)
2100 return revert ? 1 : -1;
2104 WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;