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