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, eventList) { 19 this.parentView_ = parentView; 20 this.sourceEntry_ = sourceEntry; 21 22 this.eventTypes_ = eventList; 23 this.description_ = sourceEntry.getDescription(); 24 25 this.createRow_(); 26 } 27 28 WaterfallRow.prototype = { 29 onSourceUpdated: function() { 30 this.updateRow(); 31 }, 32 33 updateRow: function() { 34 var scale = this.parentView_.getScaleFactor(); 35 // In some cases, the REQUEST_ALIVE event has been received, while the 36 // URL Request to start the job has not been received. In that case, the 37 // description obtained is incorrect. The following fixes that. 38 if (this.description_ == '') { 39 this.urlCell_.innerHTML = ''; 40 this.description_ = this.sourceEntry_.getDescription(); 41 addTextNode(this.urlCell_, this.description_); 42 } 43 44 this.rowCell_.innerHTML = ''; 45 46 var matchingEventPairs = this.findLogEntryPairs_(this.eventTypes_); 47 48 // Creates the spacing in the beginning to show start time. 49 var startTime = this.parentView_.getStartTime(); 50 var sourceEntryStartTime = this.sourceEntry_.getStartTime(); 51 var delay = sourceEntryStartTime - startTime; 52 var frontNode = addNode(this.rowCell_, 'div'); 53 setNodeWidth(frontNode, delay * scale); 54 55 var barCell = addNode(this.rowCell_, 'div'); 56 barCell.classList.add('waterfall-view-bar'); 57 58 if (this.sourceEntry_.isError()) { 59 barCell.classList.add('error'); 60 } 61 62 var currentEnd = sourceEntryStartTime; 63 for (var i = 0; i < matchingEventPairs.length; ++i) { 64 var event = matchingEventPairs[i]; 65 var startTicks = event.startEntry.time; 66 var endTicks = event.endEntry.time; 67 event.eventType = event.startEntry.type; 68 event.startTime = timeutil.convertTimeTicksToTime(startTicks); 69 event.endTime = timeutil.convertTimeTicksToTime(endTicks); 70 event.eventDuration = event.endTime - event.startTime; 71 72 // Handles the spaces between events. 73 if (currentEnd < event.startTime) { 74 var eventDuration = event.startTime - currentEnd; 75 var padNode = this.createNode_( 76 barCell, eventDuration, this.type_, 'source'); 77 } 78 79 // Creates event bars. 80 var eventType = eventTypeToCssClass_(EventTypeNames[event.eventType]); 81 var eventNode = this.createNode_( 82 barCell, event.eventDuration, eventType, event); 83 currentEnd = event.startTime + event.eventDuration; 84 } 85 86 // Creates a bar for the part after the last event. 87 if (this.getEndTime() > currentEnd) { 88 var endDuration = (this.getEndTime() - currentEnd); 89 var endNode = this.createNode_( 90 barCell, endDuration, this.type_, 'source'); 91 } 92 }, 93 94 getStartTime: function() { 95 return this.sourceEntry_.getStartTime(); 96 }, 97 98 getEndTime: function() { 99 return this.sourceEntry_.getEndTime(); 100 }, 101 102 clearPopup_: function(parentNode) { 103 parentNode.innerHTML = ''; 104 }, 105 106 // TODO(viona): Create popup at mouse location. 107 createPopup_: function(parentNode, event, eventType, duration, mouse) { 108 var tableStart = this.parentView_.getStartTime(); 109 110 var newPopup = addNode(parentNode, 'div'); 111 newPopup.classList.add('waterfall-view-popup'); 112 113 var popupList = addNode(newPopup, 'ul'); 114 popupList.classList.add('waterfall-view-popup-list'); 115 116 this.createPopupItem_( 117 popupList, 'Event Type', eventType); 118 this.createPopupItem_( 119 popupList, 'Event Duration', duration.toFixed(0) + 'ms'); 120 121 if (event != 'source') { 122 this.createPopupItem_( 123 popupList, 'Event Start Time', event.startTime - tableStart + 'ms'); 124 this.createPopupItem_( 125 popupList, 'Event End Time', event.endTime - tableStart + 'ms'); 126 } 127 128 this.createPopupItem_( 129 popupList, 'Source Start Time', 130 this.getStartTime() - tableStart + 'ms'); 131 this.createPopupItem_( 132 popupList, 'Source End Time', this.getEndTime() - tableStart + 'ms'); 133 this.createPopupItem_( 134 popupList, 'Source ID', this.sourceEntry_.getSourceId()); 135 var urlListItem = this.createPopupItem_( 136 popupList, 'Source Description: ', this.description_); 137 138 var viewWidth = $(WaterfallView.MAIN_BOX_ID).offsetWidth; 139 // Controls the overflow of extremely long URLs by cropping to window. 140 if (urlListItem.offsetWidth > viewWidth) { 141 urlListItem.style.width = viewWidth + 'px'; 142 } 143 urlListItem.classList.add('waterfall-view-popup-list-url-item'); 144 145 this.createPopupItem_( 146 popupList, 'Has Error', this.sourceEntry_.isError()); 147 148 // Fixes cases where the popup appears 'off-screen'. 149 var popupLeft = mouse.pageX + $(WaterfallView.MAIN_BOX_ID).scrollLeft; 150 var popupRight = popupLeft + newPopup.offsetWidth; 151 var viewRight = viewWidth + $(WaterfallView.MAIN_BOX_ID).scrollLeft; 152 153 if (viewRight - popupRight < 0) { 154 popupLeft = viewRight - newPopup.offsetWidth; 155 } 156 newPopup.style.left = popupLeft + 'px'; 157 158 var popupTop = mouse.pageY + $(WaterfallView.MAIN_BOX_ID).scrollTop; 159 var popupBottom = popupTop + newPopup.offsetHeight; 160 var viewBottom = $(WaterfallView.MAIN_BOX_ID).offsetHeight + 161 $(WaterfallView.MAIN_BOX_ID).scrollTop; 162 163 if (viewBottom - popupBottom < 0) { 164 popupTop = viewBottom - newPopup.offsetHeight; 165 } 166 newPopup.style.top = popupTop + 'px'; 167 }, 168 169 createPopupItem_: function(parentPopup, key, popupInformation) { 170 var popupItem = addNode(parentPopup, 'li'); 171 addTextNode(popupItem, key + ': ' + popupInformation); 172 return popupItem; 173 }, 174 175 createRow_: function() { 176 // Create a row. 177 var tr = addNode($(WaterfallView.TBODY_ID), 'tr'); 178 tr._id = this.sourceEntry_.getSourceId(); 179 this.tableRow = tr; 180 181 var idCell = addNode(tr, 'td'); 182 addTextNode(idCell, this.sourceEntry_.getSourceId()); 183 184 var urlCell = addNode(tr, 'td'); 185 urlCell.classList.add('waterfall-view-url-cell'); 186 addTextNode(urlCell, this.description_); 187 this.urlCell_ = urlCell; 188 189 // Creates the color bar. 190 191 var rowCell = addNode(tr, 'td'); 192 rowCell.classList.add('waterfall-view-row'); 193 this.rowCell_ = rowCell; 194 195 var sourceTypeString = this.sourceEntry_.getSourceTypeString(); 196 this.type_ = eventTypeToCssClass_(sourceTypeString); 197 198 this.updateRow(); 199 }, 200 201 // Generates nodes. 202 createNode_: function(parentNode, duration, eventType, event) { 203 var scale = this.parentView_.getScaleFactor(); 204 var newNode = addNode(parentNode, 'div'); 205 setNodeWidth(newNode, duration * scale); 206 newNode.classList.add(eventType); 207 newNode.addEventListener( 208 'mouseover', 209 this.createPopup_.bind(this, newNode, event, eventType, duration), 210 true); 211 newNode.addEventListener( 212 'mouseout', this.clearPopup_.bind(this, newNode), true); 213 return newNode; 214 }, 215 216 /** 217 * Finds pairs of starting and ending events of all types that are in 218 * typeList. Currently does not handle nested events. Can consider adding 219 * starting events to a stack and popping off as their close events are 220 * found. Returns an array of objects containing start/end entry pairs, 221 * in the format {startEntry, endEntry}. 222 */ 223 findLogEntryPairs_: function() { 224 var typeList = this.eventTypes_; 225 var matchingEventPairs = []; 226 var startEntries = {}; 227 var entries = this.sourceEntry_.getLogEntries(); 228 for (var i = 0; i < entries.length; ++i) { 229 var currentEntry = entries[i]; 230 var type = currentEntry.type; 231 if (typeList.indexOf(type) < 0) { 232 continue; 233 } 234 if (currentEntry.phase == EventPhase.PHASE_BEGIN) { 235 startEntries[type] = currentEntry; 236 } 237 if (startEntries[type] && currentEntry.phase == EventPhase.PHASE_END) { 238 var event = { 239 startEntry: startEntries[type], 240 endEntry: currentEntry, 241 }; 242 matchingEventPairs.push(event); 243 } 244 } 245 return matchingEventPairs; 246 }, 247 }; 248 249 function eventTypeToCssClass_(rawEventType) { 250 return rawEventType.toLowerCase().replace(/_/g, '-'); 251 } 252 253 return WaterfallRow; 254 })(); 255