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() {
9 * A WaterfallRow represents the row corresponding to a single SourceEntry
10 * displayed by the EventsWaterfallView.
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;
22 this.description_ = sourceEntry.getDescription();
27 // Offset of popup from mouse location.
28 var POPUP_OFFSET_FROM_CURSOR = 25;
30 WaterfallRow.prototype = {
31 onSourceUpdated: function() {
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_);
46 this.rowCell_.innerHTML = '';
48 var matchingEventPairs =
49 WaterfallRow.findUrlRequestEvents(this.sourceEntry_);
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);
59 var barCell = addNode(this.rowCell_, 'div');
60 barCell.classList.add('waterfall-view-bar');
62 if (this.sourceEntry_.isError()) {
63 barCell.classList.add('error');
66 var currentEnd = sourceEntryStartTime;
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;
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');
84 // Creates event bars.
85 var eventNode = this.createNode_(
86 barCell, event.eventDuration, EventTypeNames[event.eventType],
88 currentEnd = event.startTime + event.eventDuration;
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');
99 getStartTime: function() {
100 return this.sourceEntry_.getStartTime();
103 getEndTime: function() {
104 return this.sourceEntry_.getEndTime();
107 clearPopup_: function(parentNode) {
108 parentNode.innerHTML = '';
111 createPopup_: function(parentNode, event, eventType, duration, mouse) {
112 var tableStart = this.parentView_.getStartTime();
114 var newPopup = addNode(parentNode, 'div');
115 newPopup.classList.add('waterfall-view-popup');
117 var popupList = addNode(newPopup, 'ul');
118 popupList.classList.add('waterfall-view-popup-list');
120 popupList.style.maxWidth =
121 $(WaterfallView.MAIN_BOX_ID).offsetWidth * 0.5 + 'px';
123 this.createPopupItem_(
124 popupList, 'Has Error', this.sourceEntry_.isError());
126 this.createPopupItem_(
127 popupList, 'Event Type', eventType);
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');
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_);
150 urlListItem.classList.add('waterfall-view-popup-list-url-item');
152 // Fixes cases where the popup appears 'off-screen'.
153 var popupLeft = mouse.pageX - newPopup.offsetWidth;
155 popupLeft = mouse.pageX;
157 newPopup.style.left = popupLeft +
158 $(WaterfallView.MAIN_BOX_ID).scrollLeft -
159 $(WaterfallView.BAR_TABLE_ID).offsetLeft + 'px';
161 var popupTop = mouse.pageY - newPopup.offsetHeight -
162 POPUP_OFFSET_FROM_CURSOR;
164 popupTop = mouse.pageY;
166 newPopup.style.top = popupTop +
167 $(WaterfallView.MAIN_BOX_ID).scrollTop -
168 $(WaterfallView.BAR_TABLE_ID).offsetTop + 'px';
171 createPopupItem_: function(parentPopup, key, popupInformation) {
172 var popupItem = addNode(parentPopup, 'li');
173 addTextNode(popupItem, key + ': ' + popupInformation);
177 createRow_: function() {
179 var tr = addNode($(WaterfallView.BAR_TBODY_ID), 'tr');
180 tr.classList.add('waterfall-view-table-row');
182 // Creates the color bar.
184 var rowCell = addNode(tr, 'td');
185 rowCell.classList.add('waterfall-view-row');
186 this.rowCell_ = rowCell;
188 this.sourceTypeString_ = this.sourceEntry_.getSourceTypeString();
190 var infoTr = addNode($(WaterfallView.INFO_TBODY_ID), 'tr');
191 infoTr.classList.add('waterfall-view-information-row');
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;
199 var sourceCell = addNode(infoTr, 'td');
200 sourceCell.classList.add('waterfall-view-url-cell');
201 addTextNode(sourceCell, this.description_);
202 this.sourceCell_ = sourceCell;
208 createNode_: function(parentNode, duration, eventTypeString, event) {
209 var linkNode = addNode(parentNode, 'a');
210 linkNode.href = '#events&s=' + this.sourceEntry_.getSourceId();
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(
219 this.createPopup_.bind(this, newNode, event, eventTypeString,
222 newNode.addEventListener(
223 'mouseout', this.clearPopup_.bind(this, newNode), true);
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.
233 WaterfallRow.findUrlRequestEvents = function(sourceEntry) {
239 // One level down from URL Requests.
241 var httpStreamJobSources = findDependenciesOfType_(
242 sourceEntry, EventType.HTTP_STREAM_REQUEST_BOUND_TO_JOB);
244 var httpTransactionReadHeadersPairs = findEntryPairsFromSourceEntries_(
245 [sourceEntry], EventType.HTTP_TRANSACTION_READ_HEADERS);
246 eventPairs = eventPairs.concat(httpTransactionReadHeadersPairs);
248 var proxyServicePairs = findEntryPairsFromSourceEntries_(
249 httpStreamJobSources, EventType.PROXY_SERVICE);
250 eventPairs = eventPairs.concat(proxyServicePairs);
252 if (httpStreamJobSources.length > 0) {
253 for (var i = 0; i < httpStreamJobSources.length; ++i) {
254 // Two levels down from URL Requests.
256 var hostResolverImplSources = findDependenciesOfType_(
257 httpStreamJobSources[i], EventType.HOST_RESOLVER_IMPL);
259 var socketSources = findDependenciesOfType_(
260 httpStreamJobSources[i], EventType.SOCKET_POOL_BOUND_TO_SOCKET);
262 // Three levels down from URL Requests.
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);
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
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,
280 minTime = Number(minTime);
282 var tcpConnectPairs = findEntryPairsFromSourceEntries_(
283 socketSources, EventType.TCP_CONNECT);
285 var sslConnectPairs = findEntryPairsFromSourceEntries_(
286 socketSources, EventType.SSL_CONNECT);
288 var connectionPairs = tcpConnectPairs.concat(sslConnectPairs);
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) {
297 } else if (eventPair.endEntry.time > minTime) {
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 + '';
305 eventPairs.push(eventPair);
310 eventPairs.sort(function(a, b) {
311 return a.startEntry.time - b.startEntry.time;
316 function eventTypeToCssClass_(eventType) {
317 return eventType.toLowerCase().replace(/_/g, '-');
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.
324 function findEntryPairsFromSourceEntries_(sourceEntryList, eventType) {
326 for (var i = 0; i < sourceEntryList.length; ++i) {
327 var sourceEntry = sourceEntryList[i];
329 var entries = sourceEntry.getLogEntries();
330 var matchingEventPairs = findEntryPairsByType_(entries, eventType);
331 eventPairs = eventPairs.concat(matchingEventPairs);
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.
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) {
349 if (currentEntry.phase == EventPhase.PHASE_BEGIN) {
350 startEntry = currentEntry;
352 if (startEntry && currentEntry.phase == EventPhase.PHASE_END) {
354 startEntry: startEntry,
355 endEntry: currentEntry,
357 matchingEventPairs.push(event);
361 return matchingEventPairs;
365 * Returns an ordered list of SourceEntries that are dependencies for
366 * events of the given type.
368 function findDependenciesOfType_(sourceEntry, eventType) {
369 var sourceEntryList = [];
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);
379 return sourceEntryList;
383 * Returns an ordered list of events from the given sourceEntry with the
386 function findEventsInSourceEntry_(sourceEntry, eventType) {
387 var entries = sourceEntry.getLogEntries();
389 for (var i = 0; i < entries.length; ++i) {
390 var currentEntry = entries[i];
391 if (currentEntry.type == eventType) {
392 events.push(currentEntry);
399 * Follows the event to obtain the sourceEntry that is the source
402 function findSourceEntryFromEvent_(event) {
403 if (!('params' in event) || !('source_dependency' in event.params)) {
406 var id = event.params.source_dependency.id;
407 return SourceTracker.getInstance().getSourceEntry(id);