Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / net_internals / waterfall_row.js
blobee922cb6f50c8a561ed500ebd5f9690c131bf6f1
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 var WaterfallRow = (function() {
6   'use strict';
8   /**
9    * A WaterfallRow represents the row corresponding to a single SourceEntry
10    * displayed by the EventsWaterfallView.  All times are relative to the
11    * "base time" (Time of first event from any source).
12    *
13    * @constructor
14    */
16   // TODO(viona):
17   // -Support nested events.
18   // -Handle updating length when an event is stalled.
19   function WaterfallRow(parentView, sourceEntry) {
20     this.parentView_ = parentView;
21     this.sourceEntry_ = sourceEntry;
23     this.description_ = sourceEntry.getDescription();
25     this.createRow_();
26   }
28   // Offset of popup from mouse location.
29   var POPUP_OFFSET_FROM_CURSOR = 25;
31   WaterfallRow.prototype = {
32     onSourceUpdated: function() {
33       this.updateRow();
34     },
36     updateRow: function() {
37       var scale = this.parentView_.getScaleFactor();
38       // In some cases, the REQUEST_ALIVE event has been received, while the
39       // URL Request to start the job has not been received. In that case, the
40       // description obtained is incorrect. The following fixes that.
41       if (this.description_ == '') {
42         this.sourceCell_.innerHTML = '';
43         this.description_ = this.sourceEntry_.getDescription();
44         addTextNode(this.sourceCell_, this.description_);
45       }
47       this.rowCell_.innerHTML = '';
49       var matchingEventPairs =
50           WaterfallRow.findUrlRequestEvents(this.sourceEntry_);
52       // Creates the spacing in the beginning to show start time.
53       var sourceStartTicks = this.getStartTicks();
54       var frontNode = addNode(this.rowCell_, 'div');
55       frontNode.classList.add('waterfall-view-padding');
56       setNodeWidth(frontNode, sourceStartTicks * scale);
58       var barCell = addNode(this.rowCell_, 'div');
59       barCell.classList.add('waterfall-view-bar');
61       if (this.sourceEntry_.isError()) {
62         barCell.classList.add('error');
63       }
65       var currentEnd = sourceStartTicks;
67       for (var i = 0; i < matchingEventPairs.length; ++i) {
68         var event = matchingEventPairs[i];
69         var startTicks =
70             timeutil.convertTimeTicksToRelativeTime(event.startEntry.time);
71         var endTicks =
72             timeutil.convertTimeTicksToRelativeTime(event.endEntry.time);
73         event.eventType = event.startEntry.type;
74         event.startTime = startTicks;
75         event.endTime = endTicks;
76         event.eventDuration = event.endTime - event.startTime;
78         // Handles the spaces between events.
79         if (currentEnd < event.startTime) {
80           var eventDuration = event.startTime - currentEnd;
81           var padNode = this.createNode_(
82               barCell, eventDuration, this.sourceTypeString_, 'source');
83         }
85         // Creates event bars.
86         var eventNode = this.createNode_(
87             barCell, event.eventDuration, EventTypeNames[event.eventType],
88             event);
89         currentEnd = event.startTime + event.eventDuration;
90       }
92       // Creates a bar for the part after the last event.
93       var endTime = this.getEndTicks();
94       if (endTime > currentEnd) {
95         var endDuration = endTime - currentEnd;
96         var endNode = this.createNode_(
97             barCell, endDuration, this.sourceTypeString_, 'source');
98       }
99     },
101     getStartTicks: function() {
102       return timeutil.convertTimeTicksToRelativeTime(
103                  this.sourceEntry_.getStartTicks());
104     },
106     getEndTicks: function() {
107       return timeutil.convertTimeTicksToRelativeTime(
108                  this.sourceEntry_.getEndTicks());
109     },
111     clearPopup_: function(parentNode) {
112       parentNode.innerHTML = '';
113     },
115     createPopup_: function(parentNode, event, eventType, duration, mouse) {
116       var newPopup = addNode(parentNode, 'div');
117       newPopup.classList.add('waterfall-view-popup');
119       var popupList = addNode(newPopup, 'ul');
120       popupList.classList.add('waterfall-view-popup-list');
122       popupList.style.maxWidth =
123           $(WaterfallView.MAIN_BOX_ID).offsetWidth * 0.5 + 'px';
125       this.createPopupItem_(
126           popupList, 'Has Error', this.sourceEntry_.isError());
128       this.createPopupItem_(popupList, 'Event Type', eventType);
130       if (event != 'source') {
131         this.createPopupItem_(
132             popupList, 'Event Duration', duration.toFixed(0) + 'ms');
133         this.createPopupItem_(
134             popupList, 'Event Start Time', event.startTime + 'ms');
135         this.createPopupItem_(
136             popupList, 'Event End Time', event.endTime + 'ms');
137       }
138       this.createPopupItem_(popupList, 'Source Duration',
139                             this.getEndTicks() - this.getStartTicks() + 'ms');
140       this.createPopupItem_(popupList, 'Source Start Time',
141                             this.getStartTicks() + 'ms');
142       this.createPopupItem_(popupList, 'Source End Time',
143                             this.getEndTicks() + 'ms');
144       this.createPopupItem_(
145           popupList, 'Source ID', this.sourceEntry_.getSourceId());
146       var urlListItem = this.createPopupItem_(
147           popupList, 'Source Description', this.description_);
149       urlListItem.classList.add('waterfall-view-popup-list-url-item');
151       // Fixes cases where the popup appears 'off-screen'.
152       var popupLeft = mouse.pageX - newPopup.offsetWidth;
153       if (popupLeft < 0) {
154         popupLeft = mouse.pageX;
155       }
156       newPopup.style.left = popupLeft +
157           $(WaterfallView.MAIN_BOX_ID).scrollLeft -
158           $(WaterfallView.BAR_TABLE_ID).offsetLeft + 'px';
160       var popupTop = mouse.pageY - newPopup.offsetHeight -
161           POPUP_OFFSET_FROM_CURSOR;
162       if (popupTop < 0) {
163         popupTop = mouse.pageY;
164       }
165       newPopup.style.top = popupTop +
166           $(WaterfallView.MAIN_BOX_ID).scrollTop -
167           $(WaterfallView.BAR_TABLE_ID).offsetTop + 'px';
168     },
170     createPopupItem_: function(parentPopup, key, popupInformation) {
171       var popupItem = addNode(parentPopup, 'li');
172       addTextNode(popupItem, key + ': ' + popupInformation);
173       return popupItem;
174     },
176     createRow_: function() {
177       // Create a row.
178       var tr = addNode($(WaterfallView.BAR_TBODY_ID), 'tr');
179       tr.classList.add('waterfall-view-table-row');
181       // Creates the color bar.
183       var rowCell = addNode(tr, 'td');
184       rowCell.classList.add('waterfall-view-row');
185       this.rowCell_ = rowCell;
187       this.sourceTypeString_ = this.sourceEntry_.getSourceTypeString();
189       var infoTr = addNode($(WaterfallView.INFO_TBODY_ID), 'tr');
190       infoTr.classList.add('waterfall-view-information-row');
192       var idCell = addNode(infoTr, 'td');
193       idCell.classList.add('waterfall-view-id-cell');
194       var idValue = this.sourceEntry_.getSourceId();
195       var idLink = addNodeWithText(idCell, 'a', idValue);
196       idLink.href = '#events&s=' + idValue;
198       var sourceCell = addNode(infoTr, 'td');
199       sourceCell.classList.add('waterfall-view-url-cell');
200       addTextNode(sourceCell, this.description_);
201       this.sourceCell_ = sourceCell;
203       this.updateRow();
204     },
206     // Generates nodes.
207     createNode_: function(parentNode, duration, eventTypeString, event) {
208       var linkNode = addNode(parentNode, 'a');
209       linkNode.href = '#events&s=' + this.sourceEntry_.getSourceId();
211       var scale = this.parentView_.getScaleFactor();
212       var newNode = addNode(linkNode, 'div');
213       setNodeWidth(newNode, duration * scale);
214       newNode.classList.add(eventTypeToCssClass_(eventTypeString));
215       newNode.classList.add('waterfall-view-bar-component');
216       newNode.addEventListener(
217           'mouseover',
218           this.createPopup_.bind(this, newNode, event, eventTypeString,
219               duration),
220           true);
221       newNode.addEventListener(
222           'mouseout', this.clearPopup_.bind(this, newNode), true);
223       return newNode;
224     },
225   };
227   /**
228    * Identifies source dependencies and extracts events of interest for use in
229    * inlining in URL Request bars.
230    * Created as static function for testing purposes.
231    */
232   WaterfallRow.findUrlRequestEvents = function(sourceEntry) {
233     var eventPairs = [];
234     if (!sourceEntry) {
235       return eventPairs;
236     }
238     // One level down from URL Requests.
240     var httpStreamJobSources = findDependenciesOfType_(
241         sourceEntry, EventType.HTTP_STREAM_REQUEST_BOUND_TO_JOB);
243     var httpTransactionReadHeadersPairs = findEntryPairsFromSourceEntries_(
244         [sourceEntry], EventType.HTTP_TRANSACTION_READ_HEADERS);
245     eventPairs = eventPairs.concat(httpTransactionReadHeadersPairs);
247     var proxyServicePairs = findEntryPairsFromSourceEntries_(
248         httpStreamJobSources, EventType.PROXY_SERVICE);
249     eventPairs = eventPairs.concat(proxyServicePairs);
251     if (httpStreamJobSources.length > 0) {
252       for (var i = 0; i < httpStreamJobSources.length; ++i) {
253         // Two levels down from URL Requests.
255         var hostResolverImplSources = findDependenciesOfType_(
256             httpStreamJobSources[i], EventType.HOST_RESOLVER_IMPL);
258         var socketSources = findDependenciesOfType_(
259             httpStreamJobSources[i], EventType.SOCKET_POOL_BOUND_TO_SOCKET);
261         // Three levels down from URL Requests.
263         // TODO(mmenke):  Some of these may be nested in the PROXY_SERVICE
264         //                event, resulting in incorrect display, since nested
265         //                events aren't handled.
266         var hostResolverImplRequestPairs = findEntryPairsFromSourceEntries_(
267             hostResolverImplSources, EventType.HOST_RESOLVER_IMPL_REQUEST);
268         eventPairs = eventPairs.concat(hostResolverImplRequestPairs);
270         // Truncate times of connection events such that they don't occur before
271         // the HTTP_STREAM_JOB event or the PROXY_SERVICE event.
272         // TODO(mmenke):  The last HOST_RESOLVER_IMPL_REQUEST may still be a
273         //                problem.
274         var minTime = httpStreamJobSources[i].getLogEntries()[0].time;
275         if (proxyServicePairs.length > 0)
276           minTime = proxyServicePairs[0].endEntry.time;
277         // Convert to number so comparisons will be numeric, not string,
278         // comparisons.
279         minTime = Number(minTime);
281         var tcpConnectPairs = findEntryPairsFromSourceEntries_(
282             socketSources, EventType.TCP_CONNECT);
284         var sslConnectPairs = findEntryPairsFromSourceEntries_(
285             socketSources, EventType.SSL_CONNECT);
287         var connectionPairs = tcpConnectPairs.concat(sslConnectPairs);
289         // Truncates times of connection events such that they are shown after a
290         // proxy service event.
291         for (var k = 0; k < connectionPairs.length; ++k) {
292           var eventPair = connectionPairs[k];
293           var eventInRange = false;
294           if (eventPair.startEntry.time >= minTime) {
295             eventInRange = true;
296           } else if (eventPair.endEntry.time > minTime) {
297             eventInRange = true;
298             // Should not modify original object.
299             eventPair.startEntry = shallowCloneObject(eventPair.startEntry);
300             // Need to have a string, for consistency.
301             eventPair.startEntry.time = minTime + '';
302           }
303           if (eventInRange) {
304             eventPairs.push(eventPair);
305           }
306         }
307       }
308     }
309     eventPairs.sort(function(a, b) {
310       return a.startEntry.time - b.startEntry.time;
311     });
312     return eventPairs;
313   }
315   function eventTypeToCssClass_(eventType) {
316     return eventType.toLowerCase().replace(/_/g, '-');
317   }
319   /**
320    * Finds all events of input type from the input list of Source Entries.
321    * Returns an ordered list of start and end log entries.
322    */
323   function findEntryPairsFromSourceEntries_(sourceEntryList, eventType) {
324     var eventPairs = [];
325     for (var i = 0; i < sourceEntryList.length; ++i) {
326       var sourceEntry = sourceEntryList[i];
327       if (sourceEntry) {
328         var entries = sourceEntry.getLogEntries();
329         var matchingEventPairs = findEntryPairsByType_(entries, eventType);
330         eventPairs = eventPairs.concat(matchingEventPairs);
331       }
332     }
333     return eventPairs;
334   }
336   /**
337    * Finds all events of input type from the input list of log entries.
338    * Returns an ordered list of start and end log entries.
339    */
340   function findEntryPairsByType_(entries, eventType) {
341     var matchingEventPairs = [];
342     var startEntry = null;
343     for (var i = 0; i < entries.length; ++i) {
344       var currentEntry = entries[i];
345       if (eventType != currentEntry.type) {
346         continue;
347       }
348       if (currentEntry.phase == EventPhase.PHASE_BEGIN) {
349         startEntry = currentEntry;
350       }
351       if (startEntry && currentEntry.phase == EventPhase.PHASE_END) {
352         var event = {
353           startEntry: startEntry,
354           endEntry: currentEntry,
355         };
356         matchingEventPairs.push(event);
357         startEntry = null;
358       }
359     }
360     return matchingEventPairs;
361   }
363   /**
364    * Returns an ordered list of SourceEntries that are dependencies for
365    * events of the given type.
366    */
367   function findDependenciesOfType_(sourceEntry, eventType) {
368     var sourceEntryList = [];
369     if (sourceEntry) {
370       var eventList = findEventsInSourceEntry_(sourceEntry, eventType);
371       for (var i = 0; i < eventList.length; ++i) {
372         var foundSourceEntry = findSourceEntryFromEvent_(eventList[i]);
373         if (foundSourceEntry) {
374           sourceEntryList.push(foundSourceEntry);
375         }
376       }
377     }
378     return sourceEntryList;
379   }
381   /**
382    * Returns an ordered list of events from the given sourceEntry with the
383    * given type.
384    */
385   function findEventsInSourceEntry_(sourceEntry, eventType) {
386     var entries = sourceEntry.getLogEntries();
387     var events = [];
388     for (var i = 0; i < entries.length; ++i) {
389       var currentEntry = entries[i];
390       if (currentEntry.type == eventType) {
391         events.push(currentEntry);
392       }
393     }
394     return events;
395   }
397   /**
398    * Follows the event to obtain the sourceEntry that is the source
399    * dependency.
400    */
401   function findSourceEntryFromEvent_(event) {
402     if (!('params' in event) || !('source_dependency' in event.params)) {
403       return undefined;
404     } else {
405       var id = event.params.source_dependency.id;
406       return SourceTracker.getInstance().getSourceEntry(id);
407     }
408   }
410   return WaterfallRow;
411 })();